Autentica usuarios con Go

En las apps que se ejecutan en las plataformas administradas de Google Cloud, como App Engine, no es necesario que administres la autenticación de usuarios ni las sesiones, ya que puedes usar el control de acceso de Identity-Aware Proxy (IAP). IAP controla el acceso a la app y también brinda información sobre los usuarios autenticados, incluida la dirección de correo electrónico y un identificador persistente para la app en forma de encabezados HTTP nuevos.

Objetivos

  • Solicitar a los usuarios de la app de App Engine que se autentiquen mediante IAP

  • Acceder a las identidades de los usuarios en la app para ver la dirección de correo electrónico autenticada del usuario actual

Costos

En este instructivo, se usan los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso proyectado, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices este instructivo, podrás borrar los recursos creados para evitar que se te siga facturando. Para obtener más información, consulta cómo hacer una limpieza.

Antes de comenzar

  1. Accede a tu Cuenta de Google.

    Si todavía no tienes una cuenta, regístrate para obtener una nueva.

  2. En la página Selector de proyectos de Cloud Console, selecciona o crea un proyecto de Cloud.

    Ir a la página Selector de proyectos

  3. Instala e inicializa el SDK de Cloud.
  4. Prepara el entorno de desarrollo.

Configura el proyecto

  1. En la ventana de la terminal, clona el repositorio de la app de muestra en la máquina local:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
  2. Ve al directorio que contiene el código de muestra:

    cd golang-samples/getting-started/authenticating-users

Información general

En este instructivo, se usa IAP para autenticar a los usuarios. Este es solo uno de varios de los enfoques posibles. Si deseas obtener más información sobre los distintos métodos para autenticar a los usuarios, consulta la sección Conceptos de autenticación.

La app de Hello user-email-address

La app de este instructivo es una app de Hello World de App Engine mínima, con una característica diferente: en lugar de “Hello World” muestra “Hello user-email-address”, en el que user-email-address es la dirección de correo electrónico del usuario autenticado.

Esta función es posible si se examina la información autenticada que el IAP agrega a cada solicitud web que pasa a la app. Hay tres encabezados de solicitud nuevos agregados a cada solicitud web que llega a la app. Los dos primeros encabezados son strings de texto sin formato que puedes usar para identificar al usuario. El tercer encabezado es un objeto firmado de manera criptográfica con esa misma información.

  • X-Goog-Authenticated-User-Email: Es la dirección de correo electrónico de un usuario que permite identificarlo. No almacenes información personal si la app no lo requiere. Esta app no almacena datos; solo los repite al usuario.

  • X-Goog-Authenticated-User-Id: Este ID de usuario que asigna Google no muestra información sobre el usuario, pero permite que una app sepa que un usuario que accedió es el mismo que usó la app antes.

  • X-Goog-Iap-Jwt-Assertion: Puedes configurar las apps de Google Cloud para aceptar solicitudes web de otras apps en la nube, además de las solicitudes web de Internet, mediante la omisión de IAP. Si una app está configurada de esta manera, es posible que estas solicitudes tengan encabezados falsificados. En lugar de usar cualquiera de los encabezados de texto sin formato que se mencionaron antes, puedes usar y verificar este encabezado firmado de manera criptográfica para comprobar que Google haya proporcionado la información. La dirección de correo electrónico del usuario y un ID de usuario persistente están disponibles como parte de este encabezado firmado.

Si estás seguro de que la app está configurada para que solo las solicitudes web de Internet puedan acceder a ella y que nadie pueda inhabilitar el servicio de IAP para la app, la recuperación de un ID de usuario único solo requiere la siguiente línea de código:

userID := r.Header.Get("X-Goog-Authenticated-User-ID")

Sin embargo, en una app resistente se debería esperar que algo salga mal, incluida la configuración inesperada o problemas de entorno, por lo que recomendamos crear una función que use y verifique el encabezado firmado de manera criptográfica. La firma del encabezado no se puede falsificar y, cuando se verifica, se puede usar para mostrar la identificación.

Comprende cómo funciona el código

En esta sección, se explica cómo funciona el código. Si quieres ejecutar la app, puedes ir directamente a la sección Implementa la app.

  • El archivo go.mod define un módulo de Go y los módulos de los que depende.

    module github.com/GoogleCloudPlatform/golang-samples/getting-started/authenticating-users
    
        go 1.11
    
        require (
        	cloud.google.com/go v0.55.0
        	github.com/dgrijalva/jwt-go v3.2.0+incompatible
        )
        
  • El archivo app.yaml indica a App Engine qué entorno de lenguaje requiere el código.

    runtime: go112
  • La app comienza con la importación de paquetes y la definición de una función main. La función main registra un controlador de índices y, luego, inicia un servidor HTTP.

    
        // The authenticating-users program is a sample web server application that
        // extracts and verifies user identity data passed to it via Identity-Aware
        // Proxy.
        package main
    
        import (
        	"encoding/json"
        	"fmt"
        	"log"
        	"net/http"
        	"os"
        	"time"
    
        	"cloud.google.com/go/compute/metadata"
        	"github.com/dgrijalva/jwt-go"
        )
    
        // app holds the Cloud IAP certificates and audience field for this app, which
        // are needed to verify authentication headers set by Cloud IAP.
        type app struct {
        	certs map[string]string
        	aud   string
        }
    
        func main() {
        	a, err := newApp()
        	if err != nil {
        		log.Fatal(err)
        	}
    
        	http.HandleFunc("/", a.index)
    
        	port := os.Getenv("PORT")
        	if port == "" {
        		port = "8080"
        		log.Printf("Defaulting to port %s", port)
        	}
    
        	log.Printf("Listening on port %s", port)
        	if err := http.ListenAndServe(":"+port, nil); err != nil {
        		log.Fatal(err)
        	}
        }
    
        // newApp creates a new app, returning an error if either the Cloud IAP
        // certificates or the app's audience field cannot be obtained.
        func newApp() (*app, error) {
        	certs, err := certificates()
        	if err != nil {
        		return nil, err
        	}
    
        	aud, err := audience()
        	if err != nil {
        		return nil, err
        	}
    
        	a := &app{
        		certs: certs,
        		aud:   aud,
        	}
        	return a, nil
        }
        
  • La función index obtiene el valor del encabezado de aserción de JWT que IAP agregó de la solicitud entrante y llama a la función validateAssertion para validar el valor firmado de manera criptográfica. Luego la dirección de correo electrónico se usa en una respuesta web mínima.

    
        // index responds to requests with our greeting.
        func (a *app) index(w http.ResponseWriter, r *http.Request) {
        	if r.URL.Path != "/" {
        		http.NotFound(w, r)
        		return
        	}
    
        	assertion := r.Header.Get("X-Goog-IAP-JWT-Assertion")
        	if assertion == "" {
        		fmt.Fprintln(w, "No Cloud IAP header found.")
        		return
        	}
        	email, _, err := validateAssertion(assertion, a.certs, a.aud)
        	if err != nil {
        		log.Println(err)
        		fmt.Fprintln(w, "Could not validate assertion. Check app logs.")
        		return
        	}
    
        	fmt.Fprintf(w, "Hello %s\n", email)
        }
        
  • La función validateAssertion valida que la aserción se haya firmado de forma correcta y muestra la dirección de correo electrónico asociada y el ID de usuario.

    Para validar una aserción de JWT, se requiere conocer los certificados de clave pública de la entidad con la que se firmó la aserción (Google en este caso) y el público al que está destinada la aserción. En una app de App Engine, el público es una string con información de identificación del proyecto de Google Cloud. La función validateAssertion obtiene los certificados de la función certs, y la string de público de la función audience.

    
        // validateAssertion validates assertion was signed by Google and returns the
        // associated email and userID.
        func validateAssertion(assertion string, certs map[string]string, aud string) (email string, userID string, err error) {
        	token, err := jwt.Parse(assertion, func(token *jwt.Token) (interface{}, error) {
        		keyID := token.Header["kid"].(string)
    
        		_, ok := token.Method.(*jwt.SigningMethodECDSA)
        		if !ok {
        			return nil, fmt.Errorf("unexpected signing method: %q", token.Header["alg"])
        		}
    
        		cert := certs[keyID]
        		return jwt.ParseECPublicKeyFromPEM([]byte(cert))
        	})
    
        	if err != nil {
        		return "", "", err
        	}
    
        	claims, ok := token.Claims.(jwt.MapClaims)
        	if !ok {
        		return "", "", fmt.Errorf("could not extract claims (%T): %+v", token.Claims, token.Claims)
        	}
    
        	if claims["aud"].(string) != aud {
        		return "", "", fmt.Errorf("mismatched audience. aud field %q does not match %q", claims["aud"], aud)
        	}
        	return claims["email"].(string), claims["sub"].(string), nil
        }
        
  • Puedes buscar el ID numérico y el nombre del proyecto de Google Cloud y ponerlos en el código fuente de forma manual, pero la función audience lo hace por ti mediante consultas al servicio de metadatos estándar disponible para cada app de App Engine. Debido a que el servicio de metadatos es externo al código de la app, ese resultado se guarda en una variable global que se muestra sin tener que buscar metadatos en las llamadas posteriores.

    El servicio de metadatos de App Engine (y los servicios de metadatos similares de otros servicios de procesamiento de Google Cloud) es similar a un sitio web y admite consultas web estándares. Sin embargo, el servicio de metadatos no es un sitio externo, sino una función interna que muestra la información solicitada sobre la app en ejecución, por lo que es seguro usar http en lugar de solicitudes https. El servicio de metadatos se usa con el fin de obtener los identificadores actuales de Google Cloud necesarios para definir el público previsto de la aserción de JWT.

    
        // audience returns the expected audience value for this service.
        func audience() (string, error) {
        	projectNumber, err := metadata.NumericProjectID()
        	if err != nil {
        		return "", fmt.Errorf("metadata.NumericProjectID: %v", err)
        	}
    
        	projectID, err := metadata.ProjectID()
        	if err != nil {
        		return "", fmt.Errorf("metadata.ProjectID: %v", err)
        	}
    
        	return "/projects/" + projectNumber + "/apps/" + projectID, nil
        }
        
  • Para la verificación de una firma digital, se requiere el certificado de clave pública del firmante. Google proporciona un sitio web que muestra todos los certificados de clave pública que se encuentran en uso. Estos resultados se almacenan en caché en caso de que se vuelvan a necesitar en la misma instancia de la app.

    
        // certificates returns Cloud IAP's cryptographic public keys.
        func certificates() (map[string]string, error) {
        	const url = "https://www.gstatic.com/iap/verify/public_key"
        	client := http.Client{
        		Timeout: 5 * time.Second,
        	}
        	resp, err := client.Get(url)
        	if err != nil {
        		return nil, fmt.Errorf("Get: %v", err)
        	}
    
        	var certs map[string]string
        	dec := json.NewDecoder(resp.Body)
        	if err := dec.Decode(&certs); err != nil {
        		return nil, fmt.Errorf("Decode: %v", err)
        	}
    
        	return certs, nil
        }
        

Implementa la app

Ahora puedes implementar la app y, luego, habilitar IAP a fin de que los usuarios tengan que autenticarse para poder acceder a la app.

  1. En la ventana de la terminal, ve al directorio que contiene el archivo app.yaml y, luego, implementa la app en App Engine:

    gcloud app deploy
        
  2. Cuando se te solicite, selecciona una región cercana.

  3. Cuando se te pregunte si deseas continuar con la operación de implementación, ingresa Y.

    En pocos minutos, tu aplicación estará en vivo en Internet.

  4. Visualiza la app con el siguiente comando:

    gcloud app browse
        

    En el resultado, copia web-site-url, la dirección web de la app.

  5. En una ventana del navegador, pega web-site-url para abrir la app.

    No se muestra ningún correo electrónico porque todavía no usas IAP y, por lo tanto, no se envía información del usuario a la app.

Habilita IAP

Ahora que tienes una instancia de App Engine, puedes protegerla con IAP de la siguiente manera:

  1. En Google Cloud Console, ve a la página Identity-Aware Proxy.

    Ir a la página Identity-Aware Proxy

  2. Dado que es la primera vez que habilitas una opción de autenticación en este proyecto, aparecerá un mensaje en el que se indica que debes configurar la pantalla de consentimiento de OAuth para poder usar IAP.

    Haz clic en Configurar pantalla de consentimiento.

  3. En la pestaña Pantalla de consentimiento de OAuth de la página Credenciales, completa los siguientes campos:

    • En el campo Nombre de la aplicación, ingresa IAP Example.

    • En el campo Correo electrónico de asistencia, ingresa tu dirección de correo electrónico.

    • En el campo Dominio autorizado, ingresa la parte del nombre de host de la URL de la app, por ejemplo, iap-example-999999.uc.r.appspot.com. Después de ingresar el nombre de host en el campo, presiona la tecla Enter.

    • En el campo Vínculo a la página principal de la aplicación, ingresa la URL de la app, por ejemplo, https://iap-example-999999.uc.r.appspot.com/.

    • En el campo Línea de política de privacidad de la aplicación, usa la misma URL del vínculo de la página principal para fines de prueba.

  4. Haz clic en Guardar. Cuando se solicite que crees credenciales, puedes cerrar la ventana.

  5. En Cloud Console, ve a la página Identity-Aware Proxy.

    Ir a la página Identity-Aware Proxy

  6. Para actualizar la página, haz clic en Actualizar . En la página, se muestra una lista de los recursos que puedes proteger.

  7. Haz clic en la columna IAP, para activar IAP en la aplicación.

  8. En tu navegador, dirígete a web-site-url nuevamente.

  9. En lugar de la página web, hay una pantalla de acceso para autenticarse. Cuando intentes acceder, se te negará el acceso debido a que IAP no cuenta con una lista de usuarios autorizados para acceder a la app.

Agrega usuarios autorizados a la app

  1. En Cloud Console, ve a la página Identity-Aware Proxy.

    Ir a la página Identity-Aware Proxy

  2. Selecciona la casilla de verificación de la app de App Engine y, luego, haz clic en Agregar miembro.

  3. Ingresa allAuthenticatedUsers y, luego, selecciona la función Usuario de app web protegida con IAP/Cloud IAP.

  4. Haz clic en Guardar.

Ahora los usuarios que Google pueda autenticar tienen acceso a la app. Si lo deseas, puedes agregar solo algunas personas o grupos como miembros para restringir aún más el acceso:

  • Cualquier dirección de correo electrónico de Gmail o G Suite

  • Una dirección de correo electrónico de un grupo de Google

  • Un nombre de dominio de G Suite

Accede a la app

  1. En el navegador, ve a web-site-url.

  2. Para actualizar la página, haz clic en Actualizar .

  3. En la pantalla de acceso, inicia sesión con tus credenciales de Google.

    En la página, se muestra una página de “Hello user-email-address” con tu dirección de correo electrónico.

    Si ves la misma página que antes, es posible que en el navegador no se hayan actualizado por completo las solicitudes nuevas después de la habilitación de IAP. Cierra todas las ventanas del navegador, vuelva a abrirlas y vuelve a intentarlo.

Conceptos de autenticación

Existen varias maneras de autenticar a los usuarios de una app y restringir el acceso a los usuarios autorizados. En las siguientes secciones, se enumeran los métodos de autenticación más comunes, ordenados desde los que requieren mayor esfuerzo hasta los que implican menos esfuerzo para la app.

Opción Ventajas Desventajas
Autenticación de la aplicación
  • La app se puede ejecutar en cualquier plataforma, con o sin conexión a Internet.
  • Los usuarios no necesitan usar ningún otro servicio para administrar la autenticación.
  • La app debe administrar las credenciales de los usuarios de forma segura y evitar la divulgación.
  • La app debe mantener los datos de sesiones para los usuarios que hayan accedido.
  • La app debe proporcionar funciones de registro de usuario y de cambio y recuperación de contraseña.
OAuth2
  • La app se puede ejecutar en cualquier plataforma conectada a Internet, incluidas las estaciones de trabajo para desarrolladores.
  • La app no necesita funciones de registro de usuario ni de cambio o recuperación de contraseña.
  • El riesgo de divulgación de información del usuario se delega en otro servicio.
  • Las nuevas medidas de seguridad en el acceso se gestionan fuera de la aplicación.
  • Los usuarios deben registrarse con el servicio de identidad.
  • La app debe mantener los datos de sesiones para los usuarios que hayan accedido.
IAP
  • La app no necesita códigos para administrar los usuarios, la autenticación o el estado de la sesión.
  • La app no cuenta con credenciales de usuarios que se puedan vulnerar.
  • La app solo se puede ejecutar en plataformas compatibles con el servicio. En particular, con ciertos servicios de Google Cloud compatibles con IAP, como App Engine.

Autenticación administrada por la app

Con este método, la app administra todos los aspectos de la autenticación de usuarios. La app debe mantener su propia base de datos de credenciales de usuarios y administrar las sesiones. Además, debe proporcionar funciones que permitan administrar las cuentas y las contraseñas, verificar las credenciales y generar, verificar y actualizar las sesiones de los usuarios en cada acceso autenticado. En el siguiente diagrama, se ilustra el método de autenticación administrado por la app.

Flujo administrado por la aplicación

Como se muestra en el diagrama, una vez que el usuario accede, la app crea y guarda la información sobre la sesión. Cuando el usuario realiza una solicitud a la app, tiene que incluir la información de la sesión que la app debe verificar.

La principal ventaja de este enfoque es que es autónomo y está bajo el control de la app. No es necesario que la app esté disponible en Internet. La principal desventaja es que la app debe proporcionar todas las funciones de administración de cuentas y proteger todos los datos sensibles de las credenciales.

Autenticación externa con OAuth2

Una buena alternativa para controlar todo dentro de la app es usar un servicio de identidad externo, como Google, que controla toda la información y funcionalidad de la cuenta del usuario y protege las credenciales sensibles. Cuando un usuario intenta acceder a la app, la solicitud se redirecciona al servicio de identidad, que autentica al usuario y, luego, vuelve a enviar la solicitud a la app con la información de autenticación necesaria. Para obtener más información, consulta Autentícate como usuario final.

En el siguiente diagrama, se ilustra la autenticación externa con el método OAuth2.

Flujo de OAuth2

El flujo en el diagrama comienza cuando el usuario envía una solicitud para acceder a la app. En lugar de responder directamente, la app redirecciona el navegador del usuario a la plataforma de identidad de Google, en la que se muestra una página para acceder a Google. Después de acceder con éxito, el navegador se redirige a la app. Esta solicitud incluye datos que la app puede usar para buscar información sobre el usuario autenticado y responderle.

Este método tiene muchas ventajas para la app. Permite delegar todas las funciones y riesgos de la administración de cuentas al servicio externo, lo que puede mejorar el acceso y la seguridad de la cuenta sin necesidad de cambiar la app. Sin embargo, como se muestra en el diagrama anterior, la app debe tener acceso a Internet para usar este método. La app también se encarga de administrar las sesiones una vez que el usuario se autentica.

Identity-Aware Proxy

El tercer enfoque, que se analiza en este instructivo, consiste en usar IAP para controlar la autenticación y la administración de sesiones con cualquier cambio en la app. IAP intercepta las solicitudes web a la app, bloquea las no autenticadas y pasa otras con los datos de identidad del usuario agregados a cada solicitud.

El manejo de solicitudes se muestra en el diagrama siguiente.

Flujo de IAP

Las solicitudes de los usuarios se interceptan mediante IAP, que bloquea las solicitudes no autenticadas. Las solicitudes autenticadas se pasan a la app, siempre que el usuario autenticado esté en la lista de usuarios autorizados. Las solicitudes que pasan por IAP tienen encabezados agregados que permiten identificar al usuario que realizó la solicitud.

La app ya no debe controlar la información de sesiones o cuentas de usuario. Si en una operación se necesita conocer un identificador único para el usuario, se puede obtener directamente de cada solicitud web entrante. Sin embargo, solo se puede usar para los servicios de procesamiento que admiten IAP, como App Engine y los balanceadores de cargas. No puedes usar IAP en una máquina de desarrollo local.

Realiza una limpieza

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este instructivo:

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, haz lo siguiente:

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a la página Administrar recursos

  2. En la lista de proyectos, selecciona el proyecto que deseas borrar y haz clic en Borrar .
  3. En el cuadro de diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.