Tutorial sobre la autenticación de usuarios finales en Cloud Run

En este tutorial se explica cómo crear un servicio de votación que consta de lo siguiente:

  • Un cliente basado en navegador que:

    1. Usa Identity Platform para obtener un token de ID.
    2. Permite a los usuarios votar por su animal doméstico favorito.
    3. Añade ese token de ID a una solicitud al servidor de Cloud Run que procesa el voto.
  • Un servidor de Cloud Run que:

    1. Comprueba que el usuario final se ha autenticado correctamente proporcionando un token de ID válido.
    2. Procesa el voto del usuario final.
    3. Envía el voto a Cloud SQL para almacenarlo usando sus propias credenciales.
  • Una base de datos de PostgreSQL que almacena los votos.

Para simplificar las cosas, en este tutorial se usa Google como proveedor: los usuarios deben autenticarse con una cuenta de Google para obtener su token de ID. Sin embargo, puedes usar otros proveedores o métodos de autenticación para iniciar la sesión de los usuarios.

Este servicio minimiza los riesgos de seguridad mediante Secret Manager para proteger los datos sensibles que se usan para conectarse a la instancia de Cloud SQL. También usa una identidad de servicio con el mínimo de privilegios para proteger el acceso a la base de datos.

Configurar los valores predeterminados de gcloud

Para configurar gcloud con los valores predeterminados de tu servicio de Cloud Run, sigue estos pasos:

  1. Configura tu proyecto predeterminado:

    gcloud config set project PROJECT_ID

    Sustituye PROJECT_ID por el nombre del proyecto que has creado para este tutorial.

  2. Configura gcloud para la región que hayas elegido:

    gcloud config set run/region REGION

    Sustituye REGION por la región de Cloud Run compatible que quieras.

Ubicaciones de Cloud Run

Cloud Run es regional, lo que significa que la infraestructura que ejecuta tus servicios de Cloud Run se encuentra en una región específica y Google la gestiona para que esté disponible de forma redundante en todas las zonas de esa región.

Cumplir tus requisitos de latencia, disponibilidad o durabilidad son factores primordiales para seleccionar la región en la que se ejecutan tus servicios de Cloud Run. Por lo general, puedes seleccionar la región más cercana a tus usuarios, pero debes tener en cuenta la ubicación de los otros Google Cloudproductos que utiliza tu servicio de Cloud Run. Usar Google Cloud productos juntos en varias ubicaciones puede afectar a la latencia y al coste de tu servicio.

Cloud Run está disponible en las siguientes regiones:

Con sujeción a los precios del nivel 1

  • asia-east1 (Taiwán)
  • asia-northeast1 (Tokio)
  • asia-northeast2 (Osaka)
  • asia-south1 (Bombay, la India)
  • europe-north1 (Finlandia) icono de una hoja CO2 bajo
  • europe-north2 (Estocolmo) icono de una hoja CO2 bajo
  • europe-southwest1 (Madrid) icono de una hoja CO2 bajo
  • europe-west1 (Bélgica) icono de una hoja CO2 bajo
  • europe-west4 (Países Bajos) icono de una hoja CO2 bajo
  • europe-west8 (Milán)
  • europe-west9 (París) icono de una hoja CO2 bajo
  • me-west1 (Tel Aviv)
  • northamerica-south1 (México)
  • us-central1 (Iowa) icono de una hoja CO2 bajo
  • us-east1 (Carolina del Sur)
  • us-east4 (Norte de Virginia)
  • us-east5 (Columbus)
  • us-south1 (Dallas) icono de una hoja CO2 bajo
  • us-west1 (Oregón) icono de una hoja CO2 bajo

Con sujeción a los precios del nivel 2

  • africa-south1 (Johannesburgo)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seúl, Corea del Sur)
  • asia-southeast1 (Singapur)
  • asia-southeast2 (Yakarta)
  • asia-south2 (Delhi, la India)
  • australia-southeast1 (Sídney)
  • australia-southeast2 (Melbourne)
  • europe-central2 Varsovia (Polonia)
  • europe-west10 (Berlín)
  • europe-west12 (Turín)
  • europe-west2 (Londres, Reino Unido) icono de una hoja CO2 bajo
  • europe-west3 (Fráncfort, Alemania)
  • europe-west6 (Zúrich, Suiza) icono de una hoja Bajas emisiones de CO2
  • me-central1 (Doha)
  • me-central2 (Dammam)
  • northamerica-northeast1 (Montreal) icono de una hoja CO2 bajo
  • northamerica-northeast2 (Toronto) icono de una hoja CO2 bajo
  • southamerica-east1 (São Paulo, Brasil) icono de una hoja CO2 bajo
  • southamerica-west1 (Santiago, Chile) icono de una hoja CO2 bajo
  • us-west2 (Los Ángeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Si ya has creado un servicio de Cloud Run, puedes ver la región en el panel de control de Cloud Run de la Google Cloud consola.

Obtener el código de ejemplo

Para obtener el código de muestra que vas a usar, sigue estos pasos:

  1. Clona el repositorio de aplicaciones de muestra en la máquina local:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

  2. Cambia al directorio que contiene el código de ejemplo de Cloud Run:

    Node.js

    cd nodejs-docs-samples/run/idp-sql/

    Python

    cd python-docs-samples/run/idp-sql/

    Java

    cd java-docs-samples/run/idp-sql/

Visualizar la arquitectura

Diagrama de arquitectura
El diagrama muestra a un usuario final iniciando sesión a través de un diálogo de inicio de sesión de Google proporcionado por Identity Platform y, a continuación, se le redirige a Cloud Run con la identidad del usuario.
  1. Un usuario final hace la primera solicitud al servidor de Cloud Run.

  2. El cliente se carga en el navegador.

  3. El usuario proporciona las credenciales de inicio de sesión a través del cuadro de diálogo de inicio de sesión de Google de Identity Platform. Se muestra una alerta para dar la bienvenida al usuario que ha iniciado sesión.

  4. El control se redirige de nuevo al servidor. El usuario final vota mediante el cliente, que obtiene un token de ID de Identity Platform y lo añade a la cabecera de la solicitud de voto.

  5. Cuando el servidor recibe la solicitud, verifica el token de ID de Identity Platform y confirma que el usuario final está autenticado correctamente. A continuación, el servidor envía el voto a Cloud SQL con sus propias credenciales.

Información sobre el código principal

El ejemplo se implementa como cliente y servidor, tal como se describe a continuación.

Integración con Identity Platform: código del lado del cliente

En este ejemplo se usan los SDKs de Firebase para integrarse con Identity Platform con el fin de iniciar sesión y gestionar usuarios. Para conectarse a Identity Platform, el JavaScript del lado del cliente contiene la referencia a las credenciales del proyecto como un objeto de configuración e importa los SDKs de JavaScript de Firebase necesarios:

const config = {
  apiKey: 'API_KEY',
  authDomain: 'PROJECT_ID.firebaseapp.com',
};
<!-- Firebase App (the core Firebase SDK) is always required and must be listed first-->
<script src="https://www.gstatic.com/firebasejs/7.18/firebase-app.js"></script>
<!-- Add Firebase Auth service-->
<script src="https://www.gstatic.com/firebasejs/7.18/firebase-auth.js"></script>

El SDK de JavaScript de Firebase gestiona el flujo de inicio de sesión pidiendo al usuario final que inicie sesión en su cuenta de Google a través de una ventana emergente. A continuación, se les redirige de nuevo al servicio.

function signIn() {
  const provider = new firebase.auth.GoogleAuthProvider();
  provider.addScope('https://www.googleapis.com/auth/userinfo.email');
  firebase
    .auth()
    .signInWithPopup(provider)
    .then(result => {
      // Returns the signed in user along with the provider's credential
      console.log(`${result.user.displayName} logged in.`);
      window.alert(`Welcome ${result.user.displayName}!`);
    })
    .catch(err => {
      console.log(`Error during sign in: ${err.message}`);
      window.alert('Sign in failed. Retry or check your browser logs.');
    });
}

Cuando un usuario inicia sesión correctamente, el cliente usa métodos de Firebase para generar un token de ID. El cliente añade el token de ID a la cabecera Authorization de su solicitud al servidor.

async function vote(team) {
  if (firebase.auth().currentUser) {
    // Retrieve JWT to identify the user to the Identity Platform service.
    // Returns the current token if it has not expired. Otherwise, this will
    // refresh the token and return a new one.
    try {
      const token = await firebase.auth().currentUser.getIdToken();
      const response = await fetch('/', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          Authorization: `Bearer ${token}`,
        },
        body: 'team=' + team, // send application data (vote)
      });
      if (response.ok) {
        const text = await response.text();
        window.alert(text);
        window.location.reload();
      }
    } catch (err) {
      console.log(`Error when submitting vote: ${err}`);
      window.alert('Something went wrong... Please try again!');
    }
  } else {
    window.alert('User not signed in.');
  }
}

Integración con Identity Platform: código del lado del servidor

El servidor usa el SDK de administrador de Firebase para verificar el token de ID de usuario enviado desde el cliente. Si el token de ID proporcionado tiene el formato correcto, no ha caducado y está firmado correctamente, el método devuelve el token de ID decodificado. El servidor extrae el uid de Identity Platform de ese usuario.

Node.js

const firebase = require('firebase-admin');
// Initialize Firebase Admin SDK
firebase.initializeApp();

// Extract and verify Id Token from header
const authenticateJWT = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (authHeader) {
    const token = authHeader.split(' ')[1];
    // If the provided ID token has the correct format, is not expired, and is
    // properly signed, the method returns the decoded ID token
    firebase
      .auth()
      .verifyIdToken(token)
      .then(decodedToken => {
        const uid = decodedToken.uid;
        req.uid = uid;
        next();
      })
      .catch(err => {
        req.logger.error(`Error with authentication: ${err}`);
        return res.sendStatus(403);
      });
  } else {
    return res.sendStatus(401);
  }
};

Python

def jwt_authenticated(func: Callable[..., int]) -> Callable[..., int]:
    """Use the Firebase Admin SDK to parse Authorization header to verify the
    user ID token.

    The server extracts the Identity Platform uid for that user.
    """

    @wraps(func)
    def decorated_function(*args: a, **kwargs: a) -> a:
        header = request.headers.get("Authorization", None)
        if header:
            token = header.split(" ")[1]
            try:
                decoded_token = firebase_admin.auth.verify_id_token(token)
            except Exception as e:
                logger.exception(e)
                return Response(status=403, response=f"Error with authentication: {e}")
        else:
            return Response(status=401)

        request.uid = decoded_token["uid"]
        return func(*args, **kwargs)

    return decorated_function

Java

/** Extract and verify Id Token from header */
private String authenticateJwt(Map<String, String> headers) {
  String authHeader =
      (headers.get("authorization") != null)
          ? headers.get("authorization")
          : headers.get("Authorization");
  if (authHeader != null) {
    String idToken = authHeader.split(" ")[1];
    // If the provided ID token has the correct format, is not expired, and is
    // properly signed, the method returns the decoded ID token
    try {
      FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
      String uid = decodedToken.getUid();
      return uid;
    } catch (FirebaseAuthException e) {
      logger.error("Error with authentication: " + e.toString());
      throw new ResponseStatusException(HttpStatus.FORBIDDEN, "", e);
    }
  } else {
    logger.error("Error no authorization header");
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
  }
}

Conectar el servidor a Cloud SQL

El servidor se conecta al socket de dominio Unix de la instancia de Cloud SQL con el formato /cloudsql/CLOUD_SQL_CONNECTION_NAME.

Node.js

/**
 * Connect to the Cloud SQL instance through UNIX Sockets
 *
 * @param {object} credConfig The Cloud SQL connection configuration from Secret Manager
 * @returns {object} Knex's PostgreSQL client
 */
const connectWithUnixSockets = async credConfig => {
  const dbSocketPath = process.env.DB_SOCKET_PATH || '/cloudsql';
  // Establish a connection to the database
  return Knex({
    client: 'pg',
    connection: {
      user: credConfig.DB_USER, // e.g. 'my-user'
      password: credConfig.DB_PASSWORD, // e.g. 'my-user-password'
      database: credConfig.DB_NAME, // e.g. 'my-database'
      host: `${dbSocketPath}/${credConfig.CLOUD_SQL_CONNECTION_NAME}`,
    },
    ...config,
  });
};

Python

def init_unix_connection_engine(
    db_config: dict[str, int]
) -> sqlalchemy.engine.base.Engine:
    """Initializes a Unix socket connection pool for a Cloud SQL instance of PostgreSQL.

    Args:
        db_config: a dictionary with connection pool config

    Returns:
        A SQLAlchemy Engine instance.
    """
    creds = credentials.get_cred_config()
    db_user = creds["DB_USER"]
    db_pass = creds["DB_PASSWORD"]
    db_name = creds["DB_NAME"]
    db_socket_dir = creds.get("DB_SOCKET_DIR", "/cloudsql")
    cloud_sql_connection_name = creds["CLOUD_SQL_CONNECTION_NAME"]

    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # postgres+pg8000://<db_user>:<db_pass>@/<db_name>
        #                         ?unix_sock=<socket_path>/<cloud_sql_instance_name>/.s.PGSQL.5432
        sqlalchemy.engine.url.URL.create(
            drivername="postgresql+pg8000",
            username=db_user,  # e.g. "my-database-user"
            password=db_pass,  # e.g. "my-database-password"
            database=db_name,  # e.g. "my-database-name"
            query={
                "unix_sock": f"{db_socket_dir}/{cloud_sql_connection_name}/.s.PGSQL.5432"
                # e.g. "/cloudsql", "<PROJECT-NAME>:<INSTANCE-REGION>:<INSTANCE-NAME>"
            },
        ),
        **db_config,
    )
    pool.dialect.description_encoding = None
    logger.info("Database engine initialized from unix connection")

    return pool

Java

Usa la integración Spring Cloud Google Cloud PostgreSQL starter para interactuar con tus bases de datos de PostgreSQL en Cloud SQL mediante bibliotecas Spring JDBC. Configura Cloud SQL para MySQL para que configure automáticamente un bean DataSource que, junto con Spring JDBC, proporciona un bean de objeto JdbcTemplate que permite realizar operaciones como consultar y modificar una base de datos.

# Uncomment and add env vars for local development
# spring.datasource.username=${DB_USER}
# spring.datasource.password=${DB_PASSWORD}
# spring.cloud.gcp.sql.database-name=${DB_NAME}
# spring.cloud.gcp.sql.instance-connection-name=${CLOUD_SQL_CONNECTION_NAME}  
private final JdbcTemplate jdbcTemplate;

public VoteController(JdbcTemplate jdbcTemplate) {
  this.jdbcTemplate = jdbcTemplate;
}

Gestionar la configuración sensible con Secret Manager

Secret Manager proporciona un almacenamiento centralizado y seguro de datos sensibles, como la configuración de Cloud SQL. El servidor inserta las credenciales de Cloud SQL desde Secret Manager en el tiempo de ejecución mediante una variable de entorno. Consulta más información sobre cómo usar secretos con Cloud Run.

Node.js

// CLOUD_SQL_CREDENTIALS_SECRET is the resource ID of the secret, passed in by environment variable.
// Format: projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
const {CLOUD_SQL_CREDENTIALS_SECRET} = process.env;
if (CLOUD_SQL_CREDENTIALS_SECRET) {
  try {
    // Parse the secret that has been added as a JSON string
    // to retrieve database credentials
    return JSON.parse(CLOUD_SQL_CREDENTIALS_SECRET.toString('utf8'));
  } catch (err) {
    throw Error(
      `Unable to parse secret from Secret Manager. Make sure that the secret is JSON formatted: ${err}`
    );
  }
}

Python

def get_cred_config() -> dict[str, str]:
    """Retrieve Cloud SQL credentials stored in Secret Manager
    or default to environment variables.

    Returns:
        A dictionary with Cloud SQL credential values
    """
    secret = os.environ.get("CLOUD_SQL_CREDENTIALS_SECRET")
    if secret:
        return json.loads(secret)

Java

/** Retrieve config from Secret Manager */
public static HashMap<String, Object> getConfig() {
  String secret = System.getenv("CLOUD_SQL_CREDENTIALS_SECRET");
  if (secret == null) {
    throw new IllegalStateException("\"CLOUD_SQL_CREDENTIALS_SECRET\" is required.");
  }
  try {
    HashMap<String, Object> config = new Gson().fromJson(secret, HashMap.class);
    return config;
  } catch (JsonSyntaxException e) {
    logger.error(
        "Unable to parse secret from Secret Manager. Make sure that it is JSON formatted: "
            + e);
    throw new RuntimeException(
        "Unable to parse secret from Secret Manager. Make sure that it is JSON formatted.");
  }
}

Configurar Identity Platform

Identity Platform requiere una configuración manual en la Google Cloud consola.

  1. En la Google Cloud consola, habilita la API Identity Platform:

    Activar la API

  2. Configura tu proyecto:

    1. En una ventana nueva, ve a la página Plataforma de autenticación de Google > Vista general.

      Ir a Vista general

    2. Haz clic en Empezar y sigue los pasos para configurar el proyecto.

    3. En el cuadro de diálogo Información de la aplicación:

      1. Indica el nombre de la aplicación.
      2. Selecciona uno de los correos de asistencia que se muestran.
    4. En el cuadro de diálogo Audiencia, seleccione Externa.

    5. En el cuadro de diálogo Información de contacto, introduce un correo de contacto.

    6. Acepta la política de datos de usuario y haz clic en Crear.

  3. Crea y obtén tu ID de cliente y secreto de OAuth:

    1. En la Google Cloud consola, ve a la página APIs y servicios > Credenciales.

      Ir a Credenciales

    2. En la parte superior de la página, haz clic en Crear credenciales y selecciona OAuth client ID.

    3. En Tipo de aplicación, selecciona Aplicación web y escribe el nombre.

    4. Primero, haz clic en Crear.

    5. Los valores de client_id y client_secret se usarán en el siguiente paso.

  4. Configura Google como proveedor:

    1. En la Google Cloud consola, ve a la página Proveedores de identidades.

      Ir a Proveedores de identidades

    2. Haz clic en Add A Provider (Añadir proveedor).

    3. Selecciona Google en la lista.

    4. En los ajustes de configuración del SDK web, introduce los valores client_id y client_secret del paso anterior.

    5. En Configura tu aplicación, haz clic en Detalles de la configuración.

  5. Copia la configuración en tu aplicación:

    • Copia los valores de apiKey y authDomain en el static/config.js de la muestra para inicializar el SDK de cliente de Identity Platform.

Desplegar el servicio

Sigue los pasos para completar el aprovisionamiento y la implementación de la infraestructura:

  1. Crea una instancia de Cloud SQL con una base de datos PostgreSQL mediante la consola o la CLI:

    gcloud sql instances create CLOUD_SQL_INSTANCE_NAME \
        --database-version=POSTGRES_16 \
        --region=CLOUD_SQL_REGION \
        --cpu=2 \
        --memory=7680MB \
        --root-password=DB_PASSWORD
  2. Añade los valores de tus credenciales de Cloud SQL a postgres-secrets.json:

    Node.js

    {
      "CLOUD_SQL_CONNECTION_NAME": "PROJECT_ID:REGION:INSTANCE",
      "DB_NAME": "postgres",
      "DB_USER": "postgres",
      "DB_PASSWORD": "PASSWORD_SECRET"
    }
    

    Python

    {
      "CLOUD_SQL_CONNECTION_NAME": "PROJECT_ID:REGION:INSTANCE",
      "DB_NAME": "postgres",
      "DB_USER": "postgres",
      "DB_PASSWORD": "PASSWORD_SECRET"
    }
    

    Java

    {
      "spring.cloud.gcp.sql.instance-connection-name": "PROJECT_ID:REGION:INSTANCE",
      "spring.cloud.gcp.sql.database-name": "postgres",
      "spring.datasource.username": "postgres",
      "spring.datasource.password": "PASSWORD_SECRET"
    }

  3. Crea un secreto versionado con la consola o la CLI:

    gcloud secrets create idp-sql-secrets \
        --replication-policy="automatic" \
        --data-file=postgres-secrets.json
  4. Crea una cuenta de servicio para el servidor con la consola o la CLI:

    gcloud iam service-accounts create idp-sql-identity
  5. Asigna roles para acceder a Secret Manager y Cloud SQL con la consola o la CLI:

    1. Permite que la cuenta de servicio asociada al servidor acceda al secreto creado:

      gcloud secrets add-iam-policy-binding idp-sql-secrets \
        --member serviceAccount:idp-sql-identity@PROJECT_ID.iam.gserviceaccount.com \
        --role roles/secretmanager.secretAccessor
    2. Permite que la cuenta de servicio asociada al servidor acceda a Cloud SQL:

      gcloud projects add-iam-policy-binding PROJECT_ID \
        --member serviceAccount:idp-sql-identity@PROJECT_ID.iam.gserviceaccount.com \
        --role roles/cloudsql.client
  6. Crea un Artifact Registry:

    gcloud artifacts repositories create REPOSITORY \
        --repository-format docker \
        --location REGION
    • REPOSITORY es el nombre del repositorio. Los nombres de los repositorios deben ser únicos en cada ubicación de repositorio de un proyecto.
  7. Crea la imagen de contenedor con Cloud Build:

    Node.js

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/idp-sql

    Python

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/idp-sql

    Java

    En este ejemplo se usa Jib para crear imágenes de Docker con herramientas comunes de Java. Jib optimiza las compilaciones de contenedores sin necesidad de usar un Dockerfile ni de tener Docker instalado. Más información sobre cómo crear contenedores Java con Jib

    1. Usa el asistente de credenciales de gcloud para autorizar a Docker a enviar contenido a tu Artifact Registry.

      gcloud auth configure-docker

    2. Usa el complemento Jib Maven para compilar y enviar el contenedor a Artifact Registry.

      mvn compile jib:build -Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/idp-sql

  8. Despliega la imagen de contenedor en Cloud Run mediante la consola o la CLI. Ten en cuenta que el servidor se implementa para permitir el acceso sin autenticación. De esta forma, el usuario puede cargar el cliente e iniciar el proceso. El servidor verifica manualmente el token de ID añadido a la solicitud de voto y autentica al usuario final.

    gcloud run deploy idp-sql \
        --image REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/idp-sql \
        --allow-unauthenticated \
        --service-account idp-sql-identity@PROJECT_ID.iam.gserviceaccount.com \
        --add-cloudsql-instances PROJECT_ID:REGION:CLOUD_SQL_INSTANCE_NAME \
        --update-secrets CLOUD_SQL_CREDENTIALS_SECRET=idp-sql-secrets:latest

    También debes tener en cuenta las marcas --service-account, --add-cloudsql-instances y --update-secrets, que especifican la identidad de servicio, la conexión de la instancia de Cloud SQL y el nombre del secreto con la versión como variable de entorno, respectivamente.

Retoques finales

Identity Platform requiere que autorices la URL del servicio de Cloud Run como redirección permitida después de que el usuario haya iniciado sesión:

  1. Para editar el proveedor de Google, haz clic en el icono del lápiz de la página Proveedores de identidades.

  2. En el panel de la derecha, en Dominios autorizados, haz clic en Añadir dominio e introduce la URL del servicio de Cloud Run.

    Puedes encontrar la URL del servicio en los registros después de la compilación o la implementación, o bien en cualquier momento mediante el siguiente comando:

    gcloud run services describe idp-sql --format 'value(status.url)'
  3. Ve a la página APIs y servicios > Credenciales.

    1. Haz clic en el icono del lápiz situado junto a tu ID de cliente de OAuth para editarlo y, a continuación, en el botón Authorized redirect URIs click the Añadir URI.

    2. Copia y pega la siguiente URL en el campo y haz clic en el botón Guardar situado en la parte inferior de la página.

    https://PROJECT_ID.firebaseapp.com/__/auth/handler

Probar la función

Para probar el servicio completo, sigue estos pasos:

  1. En tu navegador, ve a la URL que se ha proporcionado en el paso de implementación anterior.

  2. Haz clic en el botón Iniciar sesión con Google y sigue el proceso de autenticación.

  3. ¡Vota!

    Debería tener este aspecto:

    La interfaz de usuario muestra el número de votos de cada equipo y una lista de votos.

Si decides seguir desarrollando estos servicios, recuerda que tienen acceso restringido a la gestión de identidades y accesos (IAM) al resto de Google Cloud y que tendrás que asignarles roles de IAM adicionales para acceder a muchos otros servicios.