Tras muchos intentos he conseguido implementar la autenticación de google en una app de Xamarin.Forms. Es mucha la documentación que hay para ofrecer esta funcionalidad en una aplicación web, en una aplicación nativa para Android e iOS, pero poco es lo que he encontrado asociado a Xamarin.Forms. Por tanto, he considerado interesante escribir este post.

He divido la explicación en varios puntos

  • Configuración: tareas a realizar antes de ponernos a escribir líneas de código
  • Realizando autenticación: Implementación del grueso de las funcionalidades en el proyecto común de Xamarin.Forms
  • Implementación en Android: Tareas a realizar en el proyecto de dicha plataforma
  • Implementación en iOS: Tareas a realizar en el proyecto de dicha plataforma

 

Configuración

La primera configuración que hay que realizar es la de la consola de desarrollador de google

  • Ir a la consola del api e iniciar sesión
  • En el panel de control, añadir un nuevo proyecto (o seleccionar uno existente)  
  • Desde el apartado de credenciales en la pestaña pantalla de consentimiento de OAuth especificar el nombre, icono e email de asistencia.
  • En la pestaña de credenciales, crear unos credenciales seleccionando Id de cliente de OAUTH. Será necesario crear unos para Android y otros para iOS 

Para Android hay que especificar un nombre (se puede indicar el que queramos), el nombre del paquete (se puede encontrar en el AndroidManifest.xml) y la huella digital del certificado SHA-1 que se usará para firmar el apk. Para obtenerlo he tenido que lanzar el siguiente comando en mi Mac

keytool -list -v -keystore {YOUR_KEY_STORE_PATH} –alias androiddebugkey -storepass android -keypass android Me ha servidor de ayuda esta consulta en StackOverflow

Es necesario tener en cuenta, que se necesitaran tantos Ids de cliente OAuth como firmas distintas utilicemos en nuestra aplicación Android. Es posible que se use una por entorno (desarrollo, pre-producción, producción)

Para iOS hay que especificar el Id del conjunto, que no es otra cosa que el bundleId, el cual se puede encontrar en el info.plist de la aplicación iOS

  • Una vez creados los credenciales, se genera un Id de cliente por cada uno 

 

Realizando autenticación

El primer paso a realizar en el proyecto común, es añadir los paquetes nuget que vamos a utilizar. Estos son Xamarin.Auth, para facilitarnos la tarea de autenticación. Y Newtonsoft.Json, para realizar algunas llamadas a un API REST

 

Una vez instalados los paquetes nuget que se van a necesitar en la solución, ya se puede iniciar el proceso de autenticación en google. Dado que google utiliza OAuth2, se utilizará la clase OAuth2Authenticator del paquete nuget Xamarin.Auth. Para crear un objeto de este tipo hay que especificar los siguientes parámetros:

  • ClientId: Es el identificador creado en la consola de google. Recuerda que había uno por plataforma e incluso, uno por entorno.
  • ClientSecret: No es necesario para la autenticación en google
  • Scope: Se utiliza para indicar el alcance que tendrá la autenticación. Esto es, a qué información se va a tener acceso. Podemos encontrar más información sobre los scopes en la documentación de Google.
  • AuthorizeUrl: URL de google requerida en la autenticación. Existe una web con la documentación de google asociada a este parámetro.
  • RedirectUrl: URL a la que te va a  redirigir google una vez hecha la autenticación de forma correcta. En este caso, al estar realizando la autenticación en una app, no es necesario que la URL exista, simplemente se utilizará para que Xamarin.OAuth capture la redirección a esa web y devuelva el control a nuestra app.
  • AccessTokenUrl: URL de google requerida en la autenticación. Existe una web con la documentación de google asociada a este parámetro.
  • GetUserNameAsyncFunc: No se dan detalles, porque en esta autenticación se será necesario utilizarlo, por lo que se informará a null.
  • IsUsingNativeUI: Es necesario marcarlo a true para cumplir con la nueva política de Google.

Una vez que sabemos de qué manera hay que realizar la autenticación, solo falta escribir un poco de código. Como en cada plataforma (Android e iOS, en el ejemplo) se tienen parámetros diferentes, lo que he hecho es, en la librería común crear una interfaz IGoogleAuthService que será implementada en cada una de las plataformas. Está interfaz solo tiene un método Authenticate al que se le pasa la interfaz IGoogleAuthenticationDelegate. Esta interfaz, será la view/viewModel desde el que se lanza la autenticación, que implementará 3 métodos que se lanzarán cuando dicha autenticación sea completada, fallida o cancelada.

public interface IGoogleAuthService
{
    void Autheticate(IGoogleAuthenticationDelegate googleAuthenticationDelegate);
}

public interface IGoogleAuthenticationDelegate
{
    void OnAuthenticationCompleted(GoogleOAuthToken token);
    void OnAuthenticationFailed(string message, Exception exception);
    void OnAuthenticationCanceled();
}

 

Además de estas interfaces, en la librería común he creado la clase GoogleAuthenticator, que se encarga de crear un objeto OAuth2Authenticator, que se utilizará en las implementaciones de  IGoogleAuthService en cada plataforma, y de notificar a un IGoogleAuthenticationDelegate el resultado de ésta. Asímismo, lanza el evento AuthenticationDone cuando el usuario termina su autenticación (tanto si es correcta, incorrecta o fallida)

public class GoogleAuthenticator
{
    private const string AuthorizeUrl = "https://accounts.google.com/o/oauth2/v2/auth";
    private const string AccessTokenUrl = "https://www.googleapis.com/oauth2/v4/token";
    private const bool IsUsingNativeUI = true;

    private OAuth2Authenticator _auth;
    private IGoogleAuthenticationDelegate _authenticationDelegate;

    public delegate void AuthenticationDoneHandler();
    public event AuthenticationDoneHandler AuthenticationDone;

    public GoogleAuthenticator(string clientId, string scope, string redirectUrl, IGoogleAuthenticationDelegate authenticationDelegate)
    {
        _authenticationDelegate = authenticationDelegate;

        _auth = new OAuth2Authenticator(clientId, string.Empty, scope,
                                        new Uri(AuthorizeUrl),
                                        new Uri(redirectUrl),
                                        new Uri(AccessTokenUrl),
                                        null, IsUsingNativeUI);

        _auth.Completed += OnAuthenticationCompleted;
        _auth.Error += OnAuthenticationFailed;
    }

    public OAuth2Authenticator GetAuthenticator()
    {
        return _auth;
    }

    public void OnPageLoading(Uri uri)
    {
        _auth.OnPageLoading(uri);
    }

    private void OnAuthenticationCompleted(object sender, AuthenticatorCompletedEventArgs e)
    {
        if (e.IsAuthenticated)
        {
            var token = new GoogleOAuthToken
            {
                TokenType = e.Account.Properties["token_type"],
                AccessToken = e.Account.Properties["access_token"]
            };
            _authenticationDelegate.OnAuthenticationCompleted(token);
        }
        else
        {
            _authenticationDelegate.OnAuthenticationCanceled();
        }

        if (AuthenticationDone != null)
            AuthenticationDone();
    }

    private void OnAuthenticationFailed(object sender, AuthenticatorErrorEventArgs e)
    {
        _authenticationDelegate.OnAuthenticationFailed(e.Message, e.Exception);
        if (AuthenticationDone != null)
            AuthenticationDone();
    }
}

public static class GoogleAuthenticatorHelper
{
    public static GoogleAuthenticator Auth;
}

 

Una vez realiza la autenticación, se podría obtener datos del usuario, siempre que estén dentro del scope con el que se realiza la autenticación. Para ello, en este proyecto, he creado una clase de utilidad (GoogleAccountInfoService), que devuelve el email del usuario autenticado (en este ejemplo solo se obtiene el email, pero hay muchos más datos que se podrían obtener, en función del scope utilizado)

public class GoogleAccountInfoService
{
    public async Task GetEmailAsync(string tokenType, string accessToken)
    {
        var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(tokenType, accessToken);
        var json = await httpClient.GetStringAsync("https://www.googleapis.com/userinfo/email?alt=json");
        var email = JsonConvert.DeserializeObject(json);
        return email.Data.Email;
    }
}

public class GoogleEmail
{
    public GoogleEmailData Data { get; set; }
}

public class GoogleEmailData
{
    public string Email { get; set; }
}

 

Implementación en Android

En el proyecto Android he tenido que crear dos clases. La primera implementa la interfaz IGoogleAuthService. Para completar esta implementación, basta con utilizar la clase GoogleAuthenticator del proyecto común, pasándole el clientId y redirectUrl propios de Andorid.

[assembly: Dependency(typeof(GoogleAuthService))]
namespace OAuth.Droid.Services
{
    public class GoogleAuthService : IGoogleAuthService
    {
        internal static MainActivity MainActivity { get; set; }

        public GoogleAuthService()
        {

        }

        public void Autheticate(IGoogleAuthenticationDelegate googleAuthenticationDelegate)
        {
            GoogleAuthenticatorHelper.Auth = new GoogleAuthenticator(
               YOUR_CLIENT_ID,
               YOUR_SCOPE,
               YOUR_REDIRECT_URL,
                googleAuthenticationDelegate);

            // Display the activity handling the authentication
            var authenticator = GoogleAuthenticatorHelper.Auth.GetAuthenticator();
            var intent = authenticator.GetUI(MainActivity);
            MainActivity.StartActivity(intent);
        }
    }
}

 

Como se aprecia en el código anterior, lo que se hace es abrir un browser en el que se realiza la autenticación, para controlar el resultado de ésta, es necesario crear un Interceptor, tal y como se muestra a continuación. Este paso es necesario para conseguir ser notificados de si la autenticación se ha realizado correcta o incorrectamente, o si ha sido cancelada.

[Activity(Label = "GoogleAuthInterceptor")]
[
    IntentFilter
    (
        actions: new[] { Intent.ActionView },
        Categories = new[]
        {
                Intent.CategoryDefault,
                Intent.CategoryBrowsable
        },
        DataSchemes = new[]
        {
            // First part of the redirect url (Package name)
            "YOUR_REDIRECT_URL_FIRST_PART"
        },
        DataPaths = new[]
        {
            // Second part of the redirect url (Path)
            "YOUR_REDIRECT_URL_SECOND_PART"
        }
    )
]
public class GoogleAuthInterceptor : Activity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        Android.Net.Uri uri_android = Intent.Data;

        // Convert iOS NSUrl to C#/netxf/BCL System.Uri - common API
        Uri uri_netfx = new Uri(uri_android.ToString());

        // Send the URI to the Authenticator for continuation
        GoogleAuthenticatorHelper.Auth?.OnPageLoading(uri_netfx);

        Finish();
    }
}

 

Implementación en iOS

Las modificaciones en el proyecto iOS son incluso más sencillas que en Android.

Lo primero que hay que hacer es modificar info.plist para interceptar las llamadas desde safari. Al igual que en android, la autenticación en google se hace desde el browser de sistema operativo, Safari en este caso. Para interceptar safari cuando la autenticación se ha realizado, y devolver el control a nuestra app, es necesario añadir la siguiente información en el info.plist

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>YOUR_PROJECT_NAME</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>BUNDLE_ID</string>
        </array>
        <key>CFBundleURLTypes</key>
        <string>Viewer</string>
    </dict>
</array>

 

Una vez modificado este archivo, solo falta implementar el método OpenUrl en el AppDelegate. Este método es el que será llamada cuando safari navegue a la página a la que se realiza la redirección una vez que el usuario ha sido autenticado.

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

    GoogleAuthenticatorHelper.Auth?.OnPageLoading(uri_netfx);

    return true;
}

 

Por último, al igual que en android, es necesaria la implementación de la interfaz IGoogleAuthService. Esta implementación es muy similar a la de android, ya que para realizar la utenticación también utiliza la clase GoogleAuthenticator de la librería común. Además, nos suscribimos al evento AuthenticationDone para, una vez que la autenticación se ha realizado, cerrar el browser.

[assembly: Dependency(typeof(GoogleAuthService))]
namespace OAuth.iOS.Services
{
    public class GoogleAuthService : IGoogleAuthService
    {
        public GoogleAuthService()
        {

        }

        public void Autheticate(IGoogleAuthenticationDelegate googleAuthenticationDelegate)
        {
            GoogleAuthenticatorHelper.Auth = new GoogleAuthenticator(
               YOUR_CLIENT_ID
               YOUR_SCOPE,
               YOUR_REDIRECT_URL,
               googleAuthenticationDelegate);

            GoogleAuthenticatorHelper.Auth.AuthenticationDone += Auth_AuthenticationDone;

            // Display the activity handling the authentication
            var authenticator = GoogleAuthenticatorHelper.Auth.GetAuthenticator();

            var viewController = authenticator.GetUI();
            var currentViewController = GetCurrentViewController();
            currentViewController.PresentViewController(viewController, true, null);
        }

        private void Auth_AuthenticationDone()
        {
            CloseBrowser();
        }

        public void CloseBrowser()
        {
            var currentViewController = GetCurrentViewController();
            currentViewController.DismissViewController(true, null);
        }

        private UIViewController GetCurrentViewController()
        {
            var window = UIApplication.SharedApplication.KeyWindow;
            var vc = window.RootViewController;
            while (vc.PresentedViewController != null)
            {
                vc = vc.PresentedViewController;
            }

            return vc;
        }
    }
}

 

 

Con todo lo anterior, tendríamos un proyecto Xamarin.Forms capaz de realizar la autenticación en google.

Como es habitual, he creado un proyecto en GitHub para ofrecer más detalles de la implementación que he realizado. Espero que os sea de ayuda

https://github.com/jorgediegocrespo/XamarinGoogleAuth

 

Bibliografía

http://timothelariviere.com/2017/09/01/authenticate-users-through-google-with-xamarin-auth/

https://docs.microsoft.com/es-es/xamarin/xamarin-forms/data-cloud/authentication/oauth

https://developers.google.com/identity/protocols/OAuth2InstalledApp

Google native login with Xamarin.Forms

Anuncios

One response to “Login en google desde Xamarin Forms

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.