Autenticar usuarios con Python

En esta parte del tutorial de Bookshelf para Phyton, se muestra cómo crear un flujo de inicio de sesión para los usuarios y cómo usar la información del perfil para ofrecer a los usuarios funciones personalizadas.

Con Google Identity Platform, puedes acceder fácilmente a la información sobre los usuarios y al mismo tiempo garantizar que Google administre sus credenciales de inicio de sesión de forma segura. OAuth 2.0 facilita el flujo de inicio de sesión para todos los usuarios de su aplicación y facilita el acceso de tu aplicación a información básica de perfil sobre usuarios autenticados.

Esta página forma parte de un tutorial de varias páginas. Para comenzar desde lo básico y consultar las instrucciones de configuración, ve a la aplicación Bookshelf de Python.

Crear un ID de cliente de aplicación web

Un ID de cliente de aplicación web permite que la aplicación autorice a los usuarios y acceda a las API de Google en nombre de los usuarios.

  1. Accede a la sección de credenciales en la consola de Google Cloud Platform.

  2. Haz clic en Pantalla de autorización de OAuth. Introduce Python Bookshelf App en el nombre del producto. Completa los campos opcionales relevantes. Haz clic en Guardar.

  3. Haz clic en Crear credenciales > ID de cliente de OAuth.

  4. En Tipo de aplicación, selecciona Web.

  5. En Nombre, introduce Python Bookshelf Client.

  6. En URIs de redirección autorizados, introduce las siguientes URL una a una. Sustituye [YOUR_PROJECT_ID] por el ID del proyecto:

    http://localhost:8080/oauth2callback
    http://[YOUR_PROJECT_ID].appspot.com/oauth2callback
    https://[YOUR_PROJECT_ID].appspot.com/oauth2callback
    http://[YOUR_PROJECT_ID].appspot-preview.com/oauth2callback
    https://[YOUR_PROJECT_ID].appspot-preview.com/oauth2callback

  7. Haz clic en Crear.

  8. Copia el ID de cliente y el secreto de cliente y guárdalos para usarlos más adelante.

Establecer configuración

En esta sección, se usan códigos en el directorio de 4-auth. Edita los archivos y ejecuta los siguientes comandos en este directorio.

  1. Abre config.py para editarlo.
  2. Define el valor de PROJECT_ID en el ID del proyecto, que puede verse en la consola de GCP.
  3. Ajusta DATA_BACKEND en el mismo valor que has utilizado en el tutorial sobre el uso de datos estructurados.
  4. Si utilizas Cloud SQL o MongoDB, ajusta los mismos valores de la sección Cloud SQL o Mongo que utilizaste en el paso de usar datos estructurados.
  5. Utiliza el nombre de segmento de Cloud Storage para establecer el valor CLOUD_STORAGE_BUCKET.

  6. En la sección de OAuth2 configuration, utiliza el ID y secreto de cliente que has usado anteriormente para establecer los valores de GOOGLE_OAUTH2_CLIENT_ID y GOOGLE_OAUTH2_CLIENT_SECRET.

  7. Guarda y cierra config.py.

Si usas Cloud SQL:

  1. Abre app.yaml para editarlo.
  2. Ajusta el valor de cloud_sql_instances al mismo que utilizaste en CLOUDSQL_CONNECTION_NAME de config.py . Debería tener el formato project:region:cloudsql-instance. Elimina los comentarios de toda esta línea.
  3. Guarda y cierra app.yaml.

Instalar dependencias

Introduce los siguientes comandos para crear un entorno virtual e instalar dependencias:

Linux/Mac OS X

virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt

Windows

virtualenv -p python3 env
env\scripts\activate
pip install -r requirements.txt

Ejecutar la aplicación en la máquina local:

  1. Inicia un servidor web local:

    python main.py
    
  2. Introduce la siguiente dirección en el navegador web:

    http://localhost:8080

Ahora puedes navegar por las páginas web de la aplicación, iniciar sesión con tu cuenta de Google, añadir libros y ver los libros que agregaste mediante el enlace Mis libros que aparece en la barra de navegación superior.

Presiona Control + C para salir del servidor web local.

Desplegar la aplicación en el entorno flexible de App Engine

  1. Despliega la aplicación de muestra:

    gcloud app deploy
    
  2. En el navegador web, introduce la siguiente dirección. Sustituye [YOUR_PROJECT_ID] por el ID del proyecto:

    https://[YOUR_PROJECT_ID].appspot.com
    

Si actualizas la aplicación, podrás desplegar la versión actualizada con el mismo comando utilizado para desplegar la aplicación por primera vez. El nuevo despliegue crea una nueva versión de la aplicación y la establece como la versión predeterminada. Se conservan las versiones anteriores de la aplicación, al igual que las instancias de máquina virtual asociadas. Ten en cuenta que todas estas versiones de la aplicación y las instancias de máquina virtual son recursos facturables.

Si eliminas las versiones no predeterminadas de la aplicación, puedes reducir los costes.

Para eliminar una versión de la aplicación, sigue las instrucciones que figuran a continuación:

  1. In the GCP Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

Si quieres obtener información completa sobre cómo limpiar los recursos facturables, consulta la sección de limpieza en el último paso de este tutorial.

Estructura de la aplicación

En el siguiente diagrama, se muestran los componentes de la aplicación y cómo se conectan entre ellos.

Estructura de muestra de Auth

La aplicación redirige al usuario al servicio de autenticación de Google, que a su vez lo redirige de vuelta cuando está autorizado. La aplicación almacena la información de perfil del usuario en la sesión.

Comprender el código

En esta sección se explica detalladamente el código de la aplicación y su funcionamiento.

Acerca de las sesiones

Antes de que la aplicación pueda autenticar a los usuarios, necesitas almacenar información sobre el usuario actual en una sesión de alguna forma. Flask, que incluye sesiones respaldadas por cookies encriptadas, proporciona esta función.

Para usar sesiones con Flask, es necesario configurar una clave secreta para la aplicación.

SECRET_KEY = 'secret'

Flask recomienda usar urandom para generar una clave aleatoria.

python
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'

Copia y pega el resultado final en config.py como tu clave secreta.

En producción, puedes utilizar una base de datos centralizada para almacenar sesiones de forma segura. Google Cloud Platform facilita el despliegue de muchas bases de datos, incluidas Redis o Memcache. La configuración de estos servicios está fuera del alcance de este tutorial.

Autenticar usuarios

Autenticar al usuario implica dos pasos básicos, que en conjunto se denominan flujo de servicio web:

  • Redirigir al usuario al servicio de autorización de Google.
  • Procesar la respuesta cuando Google redirige al usuario a la aplicación.

La extensión flask_util en oauth2client hace que puedas integrar OAuth2 en tu aplicación fácilmente, sin tener que desplegar el flujo.

  1. Crea una instancia de UserOAuth2.

    from oauth2client.contrib.flask_util import UserOAuth2
    
    oauth2 = UserOAuth2()

  2. Inicializa la instancia con la aplicación de Flask. Los permisos profile y el email permiten acceder a la dirección de correo electrónico del usuario y a la información básica del perfil de Google.

    # Initalize the OAuth2 helper.
    oauth2.init_app(
        app,
        scopes=['email', 'profile'],
        authorize_callback=_request_user_info)

  3. Crea una forma para que los usuarios cierren la sesión, ya que flask_util no lo proporciona de manera predeterminada.

    # Add a logout handler.
    @app.route('/logout')
    def logout():
        # Delete the user's profile and the credentials stored by oauth2.
        del session['profile']
        session.modified = True
        oauth2.storage.delete()
        return redirect(request.referrer or '/')

  4. Una vez hayas obtenido las credenciales, puedes obtener información sobre el usuario. Las credenciales incluyen un id_token que te proporciona la identificación y la dirección de correo electrónico del usuario, pero sería más útil obtener información básica sobre el perfil, como el nombre y la foto del usuario. El método de la API de Google+ people.get('me') puede proporcionar esos datos sobre cualquier usuario autenticado. UserOAuth2 tiene un argumento authorize_callback que se puede usar para ejecutar una función después de obtener las credenciales del usuario.

    def _request_user_info(credentials):
        """
        Makes an HTTP request to the Google+ API to retrieve the user's basic
        profile information, including full name and photo, and stores it in the
        Flask session.
        """
        http = httplib2.Http()
        credentials.authorize(http)
        resp, content = http.request(
            'https://www.googleapis.com/plus/v1/people/me')
    
        if resp.status != 200:
            current_app.logger.error(
                "Error while obtaining user profile: \n%s: %s", resp, content)
            return None
        session['profile'] = json.loads(content.decode('utf-8'))
    

  5. Usa la información de perfil proporcionada por la sesión en las plantillas para indicar al usuario que está conectado o desconectado:

    <p class="navbar-text navbar-right">
    {% if session.profile %}
      <a href="/logout">
        {% if session.profile.image %}
          <img class="img-circle" src="{{session.profile.image.url}}" width="24">
        {% endif %}
        {{session.profile.displayName}}
      </a>
    {% else %}
      <a href="/oauth2authorize">Login</a>
    {% endif %}
    </p>

Personalización

Ahora que tienes la información del usuario que ha iniciado sesión disponible, puedes estar al tanto de qué libros ha subido cada usuario a la base de datos.

@crud.route('/add', methods=['GET', 'POST'])
def add():
    if request.method == 'POST':
        data = request.form.to_dict(flat=True)

        # If an image was uploaded, update the data to point to the new image.
        image_url = upload_image_file(request.files.get('image'))

        if image_url:
            data['imageUrl'] = image_url

        # If the user is logged in, associate their profile with the new book.
        if 'profile' in session:
            data['createdBy'] = session['profile']['displayName']
            data['createdById'] = session['profile']['id']

        book = get_model().create(data)

        return redirect(url_for('.view', id=book['id']))

    return render_template("form.html", action="Add", book={})

Ahora que tienes esa información en la base de datos, puedes utilizarla para crear una nueva vista que muestre al usuario todos los libros que han agregado.

@crud.route("/mine")
@oauth2.required
def list_mine():
    token = request.args.get('page_token', None)
    if token:
        token = token.encode('utf-8')

    books, next_page_token = get_model().list_by_user(
        user_id=session['profile']['id'],
        cursor=token)

    return render_template(
        "list.html",
        books=books,
        next_page_token=next_page_token)

Ten en cuenta que esta vista está decorada con oauth2.required, lo que significa que solo los usuarios que tengan credenciales válidas podrán acceder a ella. Si el usuario no tiene credenciales, se loe redirigirá a través del flujo web de OAuth2.

Este código usa un nuevo método de modelo llamado list_by_user. El despliegue depende del backend de base de datos que se elija.

Datastore

def list_by_user(user_id, limit=10, cursor=None):
    ds = get_client()
    query = ds.query(
        kind='Book',
        filters=[
            ('createdById', '=', user_id)
        ]
    )

    query_iterator = query.fetch(limit=limit, start_cursor=cursor)
    page = next(query_iterator.pages)

    entities = builtin_list(map(from_datastore, page))
    next_cursor = (
        query_iterator.next_page_token.decode('utf-8')
        if query_iterator.next_page_token else None)

    return entities, next_cursor

Cloud SQL

def list_by_user(user_id, limit=10, cursor=None):
    cursor = int(cursor) if cursor else 0
    query = (Book.query
             .filter_by(createdById=user_id)
             .order_by(Book.title)
             .limit(limit)
             .offset(cursor))
    books = builtin_list(map(from_sql, query.all()))
    next_page = cursor + limit if len(books) == limit else None
    return (books, next_page)

MongoDB

def list_by_user(user_id, limit=10, cursor=None):
    cursor = int(cursor) if cursor else 0

    results = mongo.db.books\
        .find({'createdById': user_id}, skip=cursor, limit=10)\
        .sort('title')
    books = builtin_list(map(from_mongo, results))

    next_page = cursor + limit if len(books) == limit else None
    return (books, next_page)
¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...