Firebase para aplicaciones Xamarin.Forms – Parte 2. FirebaseAuth para iOS

En el anterior artículo implementamos Firebase.Auth para Android (http://albertomontesdeoca.xyz/firebase-para-aplicaciones-xamarin-forms-parte-1-firebaseauth-para-android/), hoy continuaremos con la implementación para iOS.

Configurando la consola de Firebase

Como habrás podido imaginar, lo primero que haremos será configurar la consola de Firebase de forma similar a como lo hicimos con el proyecto Android, para acceder puedes hacerlo mediante el siguiente enlace: https://firebase.google.com.

No repetiremos todos los pasos ya que están explicados en el anterior artículo, con la diferencia que ahora el fichero que nos guardaremos será: GoogleService-Info.plist.

Configurando nuestro proyecto Xamarin.Forms

Una vez descargado el fichero GoogleService-Info.plist lo añadimos a nuestro proyecto y le asignamos la acción de compilación BundleResource.

A continuación agregamos los paquetes Nugets necesarios:

  • Xamarin.Firebase.iOS.Auth
    • Nos permitirá autenticarnos en firebase
  • Xamarin.Auth
    • Nos permitirá autenticarnos con Google

Lo siguiente que debemos configurar es nuestro fichero Entitlements.plist para poder permitir guardar credenciales en nuestro aplicación iOS, para ello solo debemos editarlo y marcar la siguiente opción:

Si no lo tienes o tienes algún problema puedes seguir la siguiente guía de Xamarin: https://developer.xamarin.com/guides/ios/deployment,_testing,_and_metrics/provisioning/working-with-entitlements/

Ya el último paso será añadir una entrada a nuestro Info.plist para que la autenticación de Google funcione (después volveremos a este tema y lo entenderás mejor):

Estos valores son:

  • Identificador de nuestra aplicación
  • El valor de REVERSED_CLIENT_ID de nuestro fichero GoogleService-Info.plist

Con estos pasos ya tenemos configurado el proyecto, así que ahora empezaremos con la implementación.

Inicialización de Firebase

Lo primero que debemos hacer es inicializar Firebase, para ello en la clase AppDelegate.cs debemos modificar el método FinishedLaunched de la siguiente forma:

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    LoadApplication(new App());

    Firebase.Core.App.Configure();
    return base.FinishedLaunching(app, options);
}

Implementación del Servicio

Lo primero que debemos hacer es crear la implementación del servicio IFirebaseAuthService de forma análoga a como lo hicimos en nuestro proyecto Android, para ello utilizaremos la inyección de dependencias, quedando el servicio de la siguiente forma: 

[assembly: Dependency(typeof(FirebaseAuthService))]
namespace firebasesample.iOS.Services.FirebaseAuth
{
    public class FirebaseAuthService : IFirebaseAuthService
    {
        public string getAuthKey()
        {
            throw new NotImplementedException();
        }

        public bool IsUserSigned()
        {
            throw new NotImplementedException();
        }

        public Task<bool> Logout()
        {
            throw new NotImplementedException();
        }

        public Task<bool> SignIn(string email, string password)
        {
            throw new NotImplementedException();
        }

        public void SignInWithGoogle()
        {
            throw new NotImplementedException();
        }

        public Task<bool> SignInWithGoogle(string token)
        {
            throw new NotImplementedException();
        }

        public Task<bool> SignUp(string email, string password)
        {
            throw new NotImplementedException();
        }
    }
}

Ahora iremos poco a poco implementando cada uno de los métodos, recordemos que las llamadas ya la tenemos implementadas, por lo que conforme vayamos rellenando cada uno de los métodos ya podremos ir probándolos.

Comprobar si el usuario está logado: IsUserSigned

La implementación de este método es muy sencilla y bastante similar a la implementación en Android:

public bool IsUserSigned()
{
    var user = Auth.DefaultInstance.CurrentUser;
    return user != null;
}

Login de usuario mediante e-mail y contraseña: SignIn 

Este método y los que siguen tienen una particularidad, la implementación de Firebase en iOS está realizada con Callbacks, por lo que para seguir la implementación de nuestro servicio devolviendo un booleano si la tarea se ha completado con éxito debemos hacer un pequeño «truco».

private static bool hasLoginResult = false;
private static bool loginResult = false;
private static bool signUpResult = false;
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token;
Task t;

public async Task<bool> SignIn(string email, string password)
{
    
    Auth.DefaultInstance.SignIn(email, password, HandleAuthResultLoginHandler);
    token = tokenSource.Token;
    t = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(4000);
    }, token).Unwrap();
    await t;
    
    
    return loginResult;
}

private void HandleAuthResultLoginHandler(User user, Foundation.NSError error)
{
    if (error != null)
    {
        AuthErrorCode errorCode;
        if (IntPtr.Size == 8) // 64 bits devices
            errorCode = (AuthErrorCode)((long)error.Code);
        else // 32 bits devices
            errorCode = (AuthErrorCode)((int)error.Code);

        // Posible error codes that SignIn method with email and password could throw
        // Visit https://firebase.google.com/docs/auth/ios/errors for more information
        switch (errorCode)
        {
            case AuthErrorCode.OperationNotAllowed:
            case AuthErrorCode.InvalidEmail:
            case AuthErrorCode.UserDisabled:
            case AuthErrorCode.WrongPassword:
            default:
                loginResult = false;
                hasLoginResult = true;
                break;
        }
    }
    else
    {
        // Do your magic to handle authentication result
        loginResult = true;
        hasLoginResult = true;
    }
    tokenSource.Cancel();
}

Como veis, lo que hemos hecho es crear una tarea que espera a obtener el resultado de la operación antes de devolverlo.

Respecto al uso del callback, yo soy partidario de tenerlo en un método aparte, pero si lo queréis usar de forma anónima podéis hacerlo sin problema.

Registro de usuario: SignUp

La implementación de este método es análoga al anterior:

public async Task<bool> SignUp(string email, string password)
{

    Auth.DefaultInstance.CreateUser(email, password,HandleAuthResultHandlerSignUp);
    token = tokenSource.Token;
    t = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(4000);
    }, token).Unwrap();
    await t;
    return signUpResult;
}

private void HandleAuthResultHandlerSignUp(User user, Foundation.NSError error)
{
    if (error != null)
    {
        AuthErrorCode errorCode;
        if (IntPtr.Size == 8) // 64 bits devices
            errorCode = (AuthErrorCode)((long)error.Code);
        else // 32 bits devices
            errorCode = (AuthErrorCode)((int)error.Code);

        // Posible error codes that CreateUser method could throw
        switch (errorCode)
        {
            case AuthErrorCode.InvalidEmail:
            case AuthErrorCode.EmailAlreadyInUse:
            case AuthErrorCode.OperationNotAllowed:
            case AuthErrorCode.WeakPassword:
            default:
                signUpResult = false;
                hasLoginResult = true;
                break;
        }
    }
    else
    {
        signUpResult = true;
        hasLoginResult = true;
    }
    tokenSource.Cancel();
}

Salida de la aplicación: Logout

 

public async Task<bool> Logout()
{
    NSError error;
    var signedOut = Auth.DefaultInstance.SignOut(out error);

    if (!signedOut)
    {
        AuthErrorCode errorCode;
        if (IntPtr.Size == 8) // 64 bits devices
            errorCode = (AuthErrorCode)((long)error.Code);
        else // 32 bits devices
            errorCode = (AuthErrorCode)((int)error.Code);

        // Posible error codes that SignOut method with credentials could throw
        // Visit https://firebase.google.com/docs/auth/ios/errors for more information
        switch (errorCode)
        {
            case AuthErrorCode.KeychainError:
            default:
                return false;
                break;
        }
    }
    else{
        return true;
    }
}

Con estos métodos, acabamos la parte «básica», ahora empezamos con la última parte, que es el acceso a la aplicación mediante la cuenta de Google, que personalmente es la que más quebraderos de cabeza me ha dado.

Login mediante nuestra cuenta de Google.

Lo primero que haremos será crear una variable en nuestro servicio de tipo OAuth2Authenticator, recordemos que para logarnos con la cuenta de Google usaremos el paquete Nuget Xamarin.Auth:

public static OAuth2Authenticator XAuth;

La implementación del método de nuestro servicio es la siguiente:

public void SignInWithGoogle()
{
    XAuth = new OAuth2Authenticator(
            clientId: "2485447395-h5buvvf05c44j54cmlg3qcnrndi0fd99.apps.googleusercontent.com",
            clientSecret: "",
            scope: "profile",
            authorizeUrl: new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
            redirectUrl: new Uri("com.googleusercontent.apps.2485447395-h5buvvf05c44j54cmlg3qcnrndi0fd99:/oauth2redirect"),
            accessTokenUrl: new Uri("https://www.googleapis.com/oauth2/v4/token"),
        isUsingNativeUI: true);
    var window = UIApplication.SharedApplication.KeyWindow;
    var vc = window.RootViewController;
    XAuth.Completed += OnAuthenticationCompleted;

    XAuth.Error += OnAuthenticationFailed;

    var viewController = XAuth.GetUI();
    vc.PresentViewController(viewController, true, null);

}

Lo más complicado es saber que valores hay que poner en cada uno de los parámetros de la llamada:

  • clientId: Lo obtenemos de nuestro fichero GoogleService-Info.plist
  • redirectUrl: será la concatenación del valor de REVERSE_CLIENT_ID del fichero GoogleService-Info.plist y «:/oauth2redirect»

Esta llamada abre una ventana de Safari para logarnos con nuestra cuenta de Google, por lo que hay que manejar el resultado de esa llamada, y lo haremos desde nuestra clase AppDelegate a la que debemos añadirle los siguiente métodos:

// For iOS 9 or newer
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
    Uri uri_netfx = new Uri(url.AbsoluteString);

    // load redirect_url Page for parsing
    FirebaseAuthService.XAuth.OnPageLoading(uri_netfx);

    return true;

    
}

// For iOS 8 and older
public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
{
    Uri uri_netfx = new Uri(url.AbsoluteString);

    // load redirect_url Page for parsing
    FirebaseAuthService.XAuth.OnPageLoading(uri_netfx);

    return true;
}

Ya sólo nos queda por implementar en nuestro servicio los métodos OnAuthenticationCompleted y OnAuthenticationFailed:

private void OnAuthenticationCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
    var window = UIApplication.SharedApplication.KeyWindow;
    var vc = window.RootViewController;
    vc.DismissViewController(true, null);

    if (e.IsAuthenticated)
    {
        var access_token = e.Account.Properties["access_token"].ToString();
        var id_token = e.Account.Properties["id_token"].ToString();

        MessagingCenter.Send(FirebaseAuthService.KEY_AUTH, FirebaseAuthService.KEY_AUTH, id_token + "###" + access_token);
        
    }
    else
    {
        //Error
    }

}
private void OnAuthenticationFailed(object sender, AuthenticatorErrorEventArgs e)
{
    var window = UIApplication.SharedApplication.KeyWindow;
    var vc = window.RootViewController;
    vc.DismissViewController(true, null);
}

Con esto ya tenemos implementada la autenticación mediante el servicio de Google, y hemos uso del centro de mensajes de Xamarin, por lo que únicamente nos queda el acceder a Firebase con las credenciales que acabamos de recoger:

 public async Task<bool> SignInWithGoogle(string tokenId)
{
    String[] tokens = tokenId.Split(new string[] { "###" }, StringSplitOptions.None);
    var credential = GoogleAuthProvider.GetCredential(tokens[0], tokens[1]);
    Auth.DefaultInstance.SignIn(credential, HandleAuthResultHandlerGoogleSignin);
    token = tokenSource.Token;
    t = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(4000);
    }, token).Unwrap();
    await t;

    return loginResult;
}

private void HandleAuthResultHandlerGoogleSignin(User user, NSError error)
{

    if (error != null)
    {
        AuthErrorCode errorCode;
        if (IntPtr.Size == 8) // 64 bits devices
            errorCode = (AuthErrorCode)((long)error.Code);
        else // 32 bits devices
            errorCode = (AuthErrorCode)((int)error.Code);

        // Posible error codes that SignIn method with credentials could throw
        switch (errorCode)
        {
            case AuthErrorCode.InvalidCredential:
            case AuthErrorCode.InvalidEmail:
            case AuthErrorCode.OperationNotAllowed:
            case AuthErrorCode.EmailAlreadyInUse:
            case AuthErrorCode.UserDisabled:
            case AuthErrorCode.WrongPassword:
            default:
                loginResult = false;
                hasLoginResult = true;
                break;
        }
    }
    else
    {
        loginResult = true;
        hasLoginResult = true;
    }

    tokenSource.Cancel();
}

Pues con esto tenemos ya por fin terminado nuestro sistema de Login con Firebase y usando autenticación mediante el servicio de Google.

Podeis descargaros el código que llevamos hecho hasta el momento desde el siguiente enlace: https://github.com/jamontes79/xamarin-forms-firebase-sample/tree/1637d1b1bd2e9cfb79b5cff5a144175265c7a7a8

Espero que os haya servido de ayuda. En el próximo artículo empezaremos la implementación del uso de la base de datos de Firebase.

Publicado en Desarrollo, Xamarin y etiquetado , , .

Ingeniero Técnico en Informática de Sistemas. Apasionado de la tecnología y enfocado al desarrollo móvil

Un comentario

  1. Pingback: Firebase para aplicaciones Xamarin.Forms – Parte 3. FirebaseDataBase para Android – Alberto Montes de Oca

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.