Implementar Login de Google (Google sign) en React Native (también compatible con Expo)

Tiempo de lectura: 6 minutos

Hoy vamos a aprender cómo podemos implementar Login de Google con Google Sign para React Native y que además es compatible con Expo.

Vamos a utilizar la librería react-native-google-signin/google-signin

Primero vamos a instalar la librería necesaria:

npx expo install @react-native-google-signin/google-signin

Importante, para que la librería funcione debes tener una APP nativa. (Aquí te enseño cómo generar un build de desarrollo nativo para Expo)

Ahora tenemos que configurar nuestra carpeta Android. Si no la tenemos realizamos:

npx expo prebuild

Tal cómo indica en la documentación oficial:

Vamos a Android/build.gradle

buildscript {
    ext {
        buildToolsVersion = "a.b.c"
        minSdkVersion = x
        compileSdkVersion = y
        targetSdkVersion = z
        googlePlayServicesAuthVersion = "20.7.0" // <--- Tienes que utilizar esta versión o una más actual
    }
// ...
    dependencies {
        classpath 'com.google.gms:google-services:4.4.0' // <--- Tienes que utilizar esta versión o una más actual
    }
}

Ahora vamos a build.gradle

apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply plugin: 'com.google.gms.google-services' // <--- Añadimos esta dependencia

Ahora ya tenemos el proyecto configurado correctamente.

Ahora debemos generar los ids necesarios para utilizar el servicio. Necesitaremos un id de app iOS y otro de web (la autenticación con Android, usa el id de web).

Para crear el de web:

Ahora, hay que iniciar sesión en Google Developer Center (https://cloud.google.com/developers?hl=es-419):

Pulsas en > Comenzar gratis y te unes.

Una vez dentro, pulsas en consola.

Dentro de la consola, tienes que crear un nuevo proyecto. Pulsas en el cajón que aparece arriba a la izquierda del título dónde pone Google Cloud (en mi caso aparece ya un proyecto):

Te abre una nueva ventana y eliges Proyecto Nuevo:

Ahora añadimos el nombre que queramos y pulsar en create.

Una vez creado, seleccionamos nuestro proyecto.

Ahora pulsamos en menú de navegación:

Ahora pulsamos en APIs y servicios y seleccionamos Credenciales:

Ahora pulsamos en Crear Credenciales y elegimos ID de cliente OAuth:

Podemos crear un credencial WEB:

Como estamos utilizando la librería de Auth-Expo, tenemos que añadir esta dirección en Orígenes autorizados de JavaScript

https://auth.expo.io

Ahora tenemos que indicar la dirección de nuestra APP para redireccionar:

Ponemos nuestro nombre de usuario de expo y el campo de slug que hemos configurado en el primer paso.

https://auth.expo.io/@nombre_usuario_expo/Nombre_app

Y pulsamos en crear:

Y nos crea la clave de cliente y clave privada.

Tenemos que copiar la clave cliente que es la que utilizaremos en el siguiente paso.

Generar para iOS:

Vamos a crear un ID de cliente Oauth pero seleccionando tipo iOS.

Indicamos tipo de aplicación iOS, indicamos el nombre que queramos para identificar nuestra clave y en ID del paquete indicamos el bundleIdentifier.

Además tendremos que copiar el iOS URI Scheme que nos indica a la derecha. Y lo usaremos dentro de nuestro archivo app.json o app.config.js

{
  "ios": {
    "infoPlist": {
      "CFBundleURLTypes": [
        {
          "CFBundleURLSchemes": ["com.googleusercontent.apps.17898xxxxxx-xxxxxqhqj0exxxxxpl03xxx"]
        }
      ]
    }
  }
}

Ahora ya tenemos todo configurado para empezar a crear el código de React Native.

Vamos a crear un componente llamado LoginIDS.tsx lo voy a guardar dentro de una carpeta llamada util/

Aquí tenemos que copiar las ids generadas en los pasos anteriores:

export const idIos = "XXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com";
export const idWeb = "XXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com";

Creamos un componente llamado GoogleAuthentication.tsx (en mi caso voy a usar TypeScript)

import React, { useState,  useContext } from "react";
import { View} from "react-native";
import {
    GoogleSignin,
    GoogleSigninButton,
    statusCodes,
} from '@react-native-google-signin/google-signin';
import { idIos, idWeb } from "@/util/LoginIDS";


GoogleSignin.configure({
    webClientId: idWeb,
    iosClientId: idIos,
    scopes: ['profile', 'email'],
});


const GoogleLogin = async () => {
    await GoogleSignin.hasPlayServices();
    const userInfo = await GoogleSignin.signIn();
    return userInfo;
};


const GoogleAuthentication: React.FC = () => {

    //Para login con google:
    const [error, setError] = useState('');
    const [loading, setLoading] = useState(false);

    const handleGoogleLogin = async () => {
        setLoading(true);
        try {
            await GoogleSignin.hasPlayServices();
            const userInfo = await GoogleSignin.signIn();
            const { idToken, user } = userInfo;
            if (idToken) {
                //console.log('idToken', idToken);
                obtenerDatosUsuarioGoogle(idToken, user);
            }
            //console.log('userInfo', userInfo);

        } catch (error: any) {
            console.log('error', error);
            switch (error.code) {
                case statusCodes.SIGN_IN_CANCELLED:
                    console.log('SIGN_IN_CANCELLED');
                    // user cancelled the login flow
                    break;
                case statusCodes.IN_PROGRESS:
                    console.log('IN_PROGRESS');
                    // operation (eg. sign in) already in progress
                    break;
                case statusCodes.PLAY_SERVICES_NOT_AVAILABLE:
                    console.log('PLAY_SERVICES_NOT_AVAILABLE');
                    // play services not available or outdated
                    break;
                default:
                    console.log('default');
                // some other error happened

            }
        }
    };



    async function obtenerDatosUsuarioGoogle(idToken: string, objUser: any) {
  console.log('userData', JSON.stringify(objUser));
setLoading(false)
       
//AQUI DEBES VALIDAR CON EL SERVIDOR, RECUERDA QUE USA LA FIRMA DE APP WEB

        });
    }

    return (
        <View>
<GoogleSigninButton
  size={GoogleSigninButton.Size.Wide}
  color={GoogleSigninButton.Color.Dark}
  onPress={() => {
    handleGoogleLogin(); 
  }}
  disabled={loading}
/>
        </View>
    )
};

export default GoogleAuthentication;

Ahora voy a explicar el código:

Primero realizo todos los imports de la librería y de los ids que hemos creado anteriormente.

Ahora asigno los ids a la librería y le indico que quiero obtener el perfil y el email (puedes indicar otros scopes).

Solo se indica webClientId, iosClientID, ya que Android usará el webClientId (esto es importante para cuando tengas que validar el token en el servidor).

GoogleSignin.configure({
    webClientId: idWeb,
    iosClientId: idIos,
    scopes: ['profile', 'email'],
});

Ahora ya generamos el código de inicio de sesión, además indicamos los posibles errores:

const GoogleLogin = async () => {
    await GoogleSignin.hasPlayServices();
    const userInfo = await GoogleSignin.signIn();
    return userInfo;
};


const GoogleAuthentication: React.FC = () => {

    //Para login con google:
    const [error, setError] = useState('');
    const [loading, setLoading] = useState(false);

    const handleGoogleLogin = async () => {
        setLoading(true);
        try {
            await GoogleSignin.hasPlayServices();
            const userInfo = await GoogleSignin.signIn();
            const { idToken, user } = userInfo;
            if (idToken) {
                //console.log('idToken', idToken);
                obtenerDatosUsuarioGoogle(idToken, user);
            }
            //console.log('userInfo', userInfo);

        } catch (error: any) {
            console.log('error', error);
            switch (error.code) {
                case statusCodes.SIGN_IN_CANCELLED:
                    console.log('SIGN_IN_CANCELLED');
                    // user cancelled the login flow
                    break;
                case statusCodes.IN_PROGRESS:
                    console.log('IN_PROGRESS');
                    // operation (eg. sign in) already in progress
                    break;
                case statusCodes.PLAY_SERVICES_NOT_AVAILABLE:
                    console.log('PLAY_SERVICES_NOT_AVAILABLE');
                    // play services not available or outdated
                    break;
                default:
                    console.log('default');
                // some other error happened

            }
        }
    };

Finalmente y muy importante, debemos validar el token generado con un servidor backend:

async function obtenerDatosUsuarioGoogle(idToken: string, objUser: any) {
  console.log('userData', JSON.stringify(objUser));
setLoading(false)
       
//AQUI DEBES VALIDAR CON EL SERVIDOR, RECUERDA QUE USA LA FIRMA DE APP WEB

        });
    }

Aquí te explico una forma de validar el token obtenido: https://devcodelight.com/verificar-token-de-google-auth-google-sign-usando-python/

Si necesitas verificar el interior del token puedes usar esta web: https://jwt.io/

Finalmente he añadido el botón de Google, puedes usar el tuyo propio o el que trae la librería:

<GoogleSigninButton
  size={GoogleSigninButton.Size.Wide}
  color={GoogleSigninButton.Color.Dark}
  onPress={() => {
    handleGoogleLogin(); 
  }}
  disabled={loading}
/>

Para cerrar sesión vamos a implementar una función que nos permita hacerlo:

export async function cerrarSesionGoogle() {
    const hasPreviousSignIn = await GoogleSignin.isSignedIn();
    if (hasPreviousSignIn) {
        await GoogleSignin.signOut();
    }
}

Primero comprueba que la sesión esté iniciada, y luego la cierra.

Podemos poner esta función dentro del archivo LoginIDS.tsx que hemos creado anteriormente y llamarla desde un botón de cerrar sesión en cualquier parte de nuestra APP.

await cerrarSesionGoogle();

Deja un comentario