Autenticar usuarios con Ruby

En esta parte del tutorial de Bookshelf, 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 la aplicación y proporciona a la aplicación acceso a información básica del perfil sobre usuarios autenticados.

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

Crear una 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. Ve a la sección de credenciales en la consola de Google Cloud Platform.

  2. Haz clic en la pantalla de autorización de OAuth. Introduce Ruby 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 OAuth.

  4. En Tipo de aplicación, selecciona Aplicación web.

  5. Introduce Ruby Bookshelf Client en Nombre.

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

    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
    http://[YOUR_PROJECT_ID].appspot-preview.com/auth/google_oauth2/callback
    https://[YOUR_PROJECT_ID].appspot-preview.com/auth/google_oauth2/callback

  7. Haz clic en Crear.

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

Instalar dependencias

Introduce el siguiente comando en el directorio getting-started-ruby/4-auth:

bundle install

Establecer configuración

  1. Copia el siguiente archivo de settings de ejemplo:

    cp config/settings.example.yml config/settings.yml
    
  2. Edita settings.yml. Sustituye los marcadores de posición del ID y secreto de cliente por el ID y secreto de cliente de la aplicación web.

    default: &default
      oauth2:
        client_id: [YOUR_CLIENT_ID]
        client_secret: [YOUR_CLIENT_SECRET]
    
  3. También en settings.yml, establece los mismos valores para project_id y gcs_bucket que usaste en la sección sobre el uso de Cloud Storage.

    Por ejemplo, supongamos que el ID de cliente de aplicación web es XYZCLIENTID y el secreto de cliente es XYZCLIENTSECRET. Supongamos también que el nombre del proyecto es my-project y que el nombre del segmento de Cloud Storage es my-bucket. Entonces, la sección default del archivo settings.yml 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 aplicación de muestra para usar la misma base de datos que estableciste durante la sección sobre el uso de datos estructurados de este tutorial:

    Cloud SQL

    Edita database.yml. Elimina los comentarios de las líneas en la sección Cloud SQL del archivo. Sustituye [PLACEHOLDERS] por los valores específicos para la instancia y 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]
    
    • Sustituye [MYSQL_USER] y [MYSQL_PASS] por el nombre de usuario y contraseña de la instancia de Cloud SQL que creaste anteriormente.

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

    • Sustituye [YOUR_INSTANCE_CONNECTION_NAME] por el valor de Instance Connection Name de la instancia de Cloud SQL.

    Ejecuta migraciones:

    bundle exec rake db:migrate
    

    PostgreSQL

    Edita database.yml. Elimina los comentarios de las líneas en la sección de PostgreSQL del archivo. Sustituye los marcadores de posición your-postgresql-* por los valores específicos para la instancia y base de datos 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. Supongamos también que el nombre de la base de datos es bookshelf. Entonces, la sección de PostgreSQL del 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 la base de datos y tablas requeridas:

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

    Cloud Datastore

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

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

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

    bundle exec rake backend:datastore
    

Ejecutar la aplicación en la máquina local

  1. Inicia un servidor web local:

    bundle exec rails server
    
  2. Introduce la siguiente dirección en el navegador web:

    http://localhost:3000

Ahora puedes navegar por las páginas web de la aplicación, iniciar sesión con tu cuenta de Google, agregar libros y ver los libros que agregaste mediante el enlace Mis libros 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. Compila los recursos de JavaScript para la producción:

    RAILS_ENV=production bundle exec rake assets:precompile
    
  2. Despliega la aplicación de muestra:

    gcloud app deploy
    
  3. Introduce la siguiente dirección en el navegador web. Sustituye [YOUR_PROJECT_ID] por el ID del proyecto:

    https://[YOUR_PROJECT_ID].appspot.com
    

Si actualizas la aplicación, puedes desplegar la versión actualizada al introducir el mismo comando que utilizaste para desplegar la aplicación por primera vez. El nuevo despliegue crea una nueva versión de la aplicación y la promociona a la versión predeterminada. Las versiones anteriores de la aplicación se mantienen, al igual que las instancias de VM asociadas. Ten en cuenta que todas estas versiones de aplicaciones e instancias de VM 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 a continuación:

  1. En Cloud Console, ve a la página Versiones de App Engine.

    Ir a la página Versiones

  2. Selecciona la casilla de verificación de la versión no predeterminada de la app que deseas borrar.
  3. Haz clic en Borrar para borrar la versión de la app.

Si quieres obtener información completa sobre cómo limpiar los recursos facturables, consulta la sección sobre limpiar los recursos 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 autenticación

Información sobre el código

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

Inicio de sesión del usuario

El formulario de la página de inicio de la aplicación presenta un nuevo enlace para que los usuarios puedan iniciar sesión:

<%= link_to "Login", login_path %>

La ruta /login está configurada en config/routes.rb:

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

Al hacer clic en Iniciar sesión, se te redirige a la pantalla de autorización del usuario de Google OAuth 2.0. Las gemas de Ruby OmniAuth y Google OAuth2 son compatibles para el flujo de inicio de sesión:

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

El archivo para inicializar omniauth.rb configura OmniAuth de tal modo que use Google OAuth 2.0 para el inicio de sesión del usuario. El código en omniauth.rb lee las configuraciones de la sección oauth2 de 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 autorización del usuario de Google OAuth 2.0, el servicio de autorización emite una petición HTTP GET para la ruta /auth/google_oauth2/callback de la aplicación de muestra, que está configurada en routes.rb.

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

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

El controlador para la ruta de devolución de llamada es el método create de la clase SessionController la aplicación de muestra. La variable de petición omniauth.auth proporciona la información de perfil, que incluye el identificador, nombre visible e imagen de perfil. El método create lee la información de 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 de inicio de la aplicación contiene nuevos elementos para mostrar información sobre el usuario que ha iniciado sesión:

<% 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

Listar los libros de un usuario

Cuando un usuario que ha iniciado sesión agrega un nuevo libro, su ID de usuario se asocia al 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 de inicio de la aplicación contiene un nuevo enlace de Mine, de modo que los usuarios que han iniciado sesión solo pueden ver los libros que ellos mismos han agregado:

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

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

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

Cierre de sesión del usuario

El formulario de la página de inicio de la aplicación presenta un nuevo enlace de logout para que los usuarios puedan iniciar sesión:

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

La ruta logout_path está configurada en config/routes.rb:

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

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

def destroy
  session.delete :user

  redirect_to root_path
end