Autenticación de usuarios con Ruby

Esta parte del instructivo de Bookshelf muestra cómo crear un flujo de acceso para los usuarios y cómo usar la información del perfil para ofrecer a los usuarios una funcionalidad personalizada.

Mediante la Plataforma de Google Identity, podrás acceder fácilmente a la información de tus usuarios y, a la vez, garantizar que Google administra sus credenciales de acceso de manera segura. Con OAuth 2.0, brindar un flujo de acceso a todos los usuarios de tu app es fácil y, además, permite a tu aplicación acceder a la información del perfil básico de los usuarios autenticados.

Esta página forma parte de un instructivo de varias páginas. Para comenzar desde el principio y leer las instrucciones de configuración, ve a la app de Bookshelf para Ruby.

Cómo crear un ID de cliente de aplicación web

Mediante un ID de cliente de aplicación web, tu aplicación puede autorizar usuarios y acceder a las API de Google en nombre de ellos.

  1. En Google Cloud Platform Console, ve a Credenciales.

    Credenciales.

  2. Haz clic en la pantalla de consentimiento de OAuth. En el nombre del producto, ingresa Ruby Bookshelf App.

  3. En Dominios autorizados, agrega el nombre de tu aplicación de App Engine como [YOUR_PROJECT_ID].appspot.com. Reemplaza [YOUR_PROJECT_ID] por el ID de tu proyecto de GCP.

  4. Llena todos los campos opcionales que sean relevantes. Haz clic en Guardar.

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

  6. En la lista desplegable Tipo de aplicación, haz clic en Aplicación web.

  7. En el campo Nombre, ingresa Ruby Bookshelf Client.

  8. En el campo URI de redireccionamiento autorizados, ingresa las siguientes URL, una a la vez.

    http://localhost:3000/auth/google_oauth2/callback
    http://[YOUR_PROJECT_ID].appspot.com/auth/google_oauth2/callback
    https://[YOUR_PROJECT_ID].appspot.com/auth/google_oauth2/callback

  9. Haz clic en Crear.

  10. Copia el ID de cliente y el secreto de cliente y guárdalos para después.

Instalación de dependencias

Ve al directorio getting-started-ruby/4-auth e ingresa este comando:

bundle install

Configuraciones

  1. Copia el archivo de configuración de ejemplo:

    cp config/settings.example.yml config/settings.yml
    
  2. Edita el archivo settings.yml. Reemplaza el ID de cliente y los marcadores de posición secretos por el secreto de cliente y el ID de cliente de tu aplicación web.

    default: &default
      oauth2:
        client_id: [YOUR_CLIENT_ID]
        client_secret: [YOUR_CLIENT_SECRET]
    
  3. Además, en el archivo settings.yml, configura en project_id y gcs_bucket los mismos valores que usaste en la sección Cómo usar Cloud Storage.

    Por ejemplo, supongamos que el ID de cliente de tu aplicación web es XYZCLIENTID y el secreto de cliente es XYZCLIENTSECRET. Asimismo, supongamos que el nombre de tu proyecto es my-project y que el nombre de tu depósito de Cloud Storage es my-bucket. Luego, la sección predeterminada de tu archivo settings.yml file se vería así:

    default: &default
      project_id: my-project
      gcs_bucket: my-bucket
      oauth2:
        client_id: XYZCLIENTID
        client_secret: XYZCLIENTSECRET
    
  4. Copia el archivo database.example.yml:

    cp config/database.example.yml config/database.yml
    
  5. Configura la app de muestra para que use la misma base de datos que configuraste durante la sección Uso de datos estructurados de este instructivo:

    Cloud SQL

    Edita el archivo database.yml. Quita los comentarios de las líneas en la sección de Cloud SQL del archivo. Reemplaza los [PLACEHOLDERS] por los valores específicos de tu instancia y tu base de datos de Cloud SQL.

     mysql_settings: &mysql_settings
       adapter: mysql2
       encoding: utf8
       pool: 5
       timeout: 5000
       username: [MYSQL_USER]
       password: [MYSQL_PASS]
       database: [MYSQL_DATABASE]
       socket: /cloudsql/[YOUR_INSTANCE_CONNECTION_NAME]
    
    • Reemplaza [MYSQL_USER] y [MYSQL_PASS] por el nombre de usuario y la contraseña de la instancia de Cloud SQL que creaste anteriormente.

    • Reemplaza [MYSQL_DATABASE] por el nombre de la base de datos que creaste anteriormente.

    • Reemplaza [YOUR_INSTANCE_CONNECTION_NAME] por Instance Connection Name, correspondiente a tu instancia de Cloud SQL.

    Ejecuta las migraciones:

    bundle exec rake db:migrate
    

    PostgreSQL

    Edita el archivo database.yml. Quita los comentarios a las líneas en la sección de PostgreSQL del archivo. Reemplaza los marcadores de posición your-postgresql-* por los valores específicos de tu instancia y base de datos de PostgreSQL. Por ejemplo, supongamos que la dirección IPv4 es 173.194.230.44, el nombre de usuario es postgres y la contraseña es pword123. Además, supongamos que el nombre de la base de datos es bookshelf. Entonces, la sección de PostgreSQL de tu archivo database.yml se vería así:

    # PostgreSQL Sample Database Configuration
    # ----------------------------------------
      adapter: postgresql
      encoding: unicode
      pool: 5
      username: postgres
      password: pword123
      host: 173.194.230.44
      database: bookshelf
    

    Crea las bases de datos y tablas obligatorias:

    bundle exec rake db:create
    bundle exec rake db:migrate
    

    Cloud Datastore

    Edita el archivo database.yml. Quita los comentarios de la única línea en la sección de Cloud Datastore del archivo. Reemplaza your-project-id por el ID del proyecto. Por ejemplo, supongamos que el ID del proyecto es my-project; la parte de Cloud Datastore del archivo database.yml se vería así:

    # Google Cloud Datastore Sample Database Configuration
    # ----------------------------------------------------
    dataset_id: my-project
    

    Ejecuta una tarea de rake para copiar los archivos de proyecto de muestra de Cloud Datastore:

    bundle exec rake backend:datastore
    

Ejecución de la app en la máquina local

  1. Inicia un servidor web local:

    bundle exec rails server
    
  2. En el navegador web, ingresa la siguiente dirección:

    http://localhost:3000

Ahora podrás navegar por las páginas web de la app, acceder con tu cuenta de Google, agregar libros y ver los libros que agregaste mediante el vínculo Mis libros en la barra de navegación superior.

Presiona Control+C para salir del servidor web local.

Implementación de la app en el entorno flexible de App Engine

  1. Compila recursos de JavaScript para producción:

    RAILS_ENV=production bundle exec rake assets:precompile
    
  2. Implementa la app de muestra:

    gcloud app deploy
    
  3. En el navegador web, ingresa la siguiente dirección. Reemplaza [YOUR_PROJECT_ID] por el ID del proyecto:

    https://[YOUR_PROJECT_ID].appspot.com
    

Si actualizas tu app, podrás implementar la versión actualizada mediante el mismo comando que usaste para implementar la app por primera vez. La implementación nueva crea una versión nueva de tu app y la convierte a la versión predeterminada. Las versiones anteriores de la app se conservan, al igual que sus instancias de VM asociadas. Ten en cuenta que todas estas instancias de VM y versiones de la app son recursos facturables.

Para reducir costos, borra las versiones no predeterminadas de la app.

Para borrar una versión de una app, haz lo siguiente:

  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.

Para obtener toda la información acerca de la limpieza de los recursos facturables, consulta la sección Limpieza en el paso final de este instructivo.

Estructura de la aplicación

El siguiente diagrama muestra los componentes de la aplicación y la manera en que se conectan entre sí.

Estructura de muestra de Auth

Comprensión del código

Esta sección explica el código de muestra y su funcionamiento.

Acceso de usuarios

El formulario de la página principal de la app tiene un vínculo nuevo desde el cual los usuarios pueden acceder.

<%= link_to "Login", login_path %>

La ruta /login se configura en config/routes.rb:

get "/login", to: redirect("/auth/google_oauth2")

Si haces clic en Acceder, se te redirigirá a la pantalla de consentimiento del usuario de Google OAuth 2.0. Las gemas de Ruby de OmniAuth y Google OAuth2 ofrecen asistencia para el flujo de acceso:

gem "omniauth"
gem "omniauth-google-oauth2"

El archivo inicializador omniauth.rb configura OmniAuth para usar Google OAuth 2.0 para el acceso de los usuarios. El código en el archivo omniauth.rb lee la configuración desde la sección oauth2 del archivo config/settings.yml.

Rails.application.config.middleware.use OmniAuth::Builder do
  config = Rails.application.config.x.settings["oauth2"]

  provider :google_oauth2, config["client_id"],
                           config["client_secret"],
                           image_size: 150
end

Al hacer clic en Permitir en la pantalla de consentimiento del usuario de Google OAuth 2.0, el servicio de autorización emite una solicitud HTTP GET para la ruta /auth/google_oauth2/callback de la app de muestra, la que se configura en routes.rb.

get "/auth/google_oauth2/callback", to: "sessions#create"

resource :session, only: [:create, :destroy]

El controlador de la ruta de devolución de llamada es el método create de la clase SessionController de la app de muestra. La variable de solicitud omniauth.auth brinda la información del perfil, la cual incluye el identificador, el nombre comercial y la imagen de perfil. El método create lee la información del perfil y la almacena en un objeto User que se serializa en la sesión del usuario:

class SessionsController < ApplicationController

  # Handle Google OAuth 2.0 login callback.
  #
  # GET /auth/google_oauth2/callback
  def create
    user_info = request.env["omniauth.auth"]

    user           = User.new
    user.id        = user_info["uid"]
    user.name      = user_info["info"]["name"]
    user.image_url = user_info["info"]["image"]

    session[:user] = Marshal.dump user

    redirect_to root_path
  end

La página principal de la app tiene elementos nuevos que mostrarán la información acerca del usuario que accedió:

<% if logged_in? %>
  <% if current_user.image_url %>
    <%= image_tag current_user.image_url, class: "img-circle", width: 24 %>
  <% end %>
  <span>
    <%= current_user.name %> &nbsp;
    <%= link_to "(logout)", logout_path %>
  </span>

Los métodos de ayuda logged_in? y current_user leen el objeto User en la sesión del usuario:

class ApplicationController < ActionController::Base
  helper_method :logged_in?, :current_user

  def logged_in?
    session.has_key? :user
  end

  def current_user
    Marshal.load session[:user] if logged_in?
  end

Cómo listar los libros de un usuario

Cuando un usuario que accedió agrega un libro nuevo, su ID de usuario se asocia con el libro:

def create
  @book = Book.new book_params

  @book.creator_id = current_user.id if logged_in?

  if @book.save
    flash[:success] = "Added Book"
    redirect_to book_path(@book)
  else
    render :new
  end
end

La página principal de la app tiene un vínculo Mine nuevo, por lo que los usuarios que accedieron solo pueden ver los libros que agregaron:

<% if logged_in? %>
  <li><%= link_to "Mine", user_books_path %></li>
<% end %>

La acción index de las consultas UserBooksController de libros que agregó el usuario que accedió:

Cloud SQL / PostgreSQL

@books = Book.where(creator_id: current_user.id).
              limit(PER_PAGE).offset(PER_PAGE * page)

Cloud Datastore

@books, @more = Book.query creator_id: current_user.id,
                           limit: PER_PAGE,
                           cursor: params[:more]
def self.query options = {}
  query = Google::Cloud::Datastore::Query.new
  query.kind "Book"
  query.limit options[:limit]   if options[:limit]
  query.cursor options[:cursor] if options[:cursor]

  if options[:creator_id]
    query.where "creator_id", "=", options[:creator_id]
  end

Salida de usuarios

El formulario de la página principal de la app tiene un vínculo logout nuevo para que los usuarios puedan salir.

<%= link_to "(logout)", logout_path %>

La ruta logout_path se configura en config/routes.rb:

get "/logout", to: "sessions#destroy"

La acción destroy de SessionsController borra al user de la sesión:

def destroy
  session.delete :user

  redirect_to root_path
end
¿Te sirvió esta página? Envíanos tu opinión: