Omniauth + Devise + Rails Tutorial
Tags: ruby, rails, omniauth, devise
Published: 2021-10-04 21:39:37


# Una breve introducción.

Que tal buen día, me presento mi nombre es Daniel o Dany para los compas. Es mi primera vez escribiendo por aquí por lo tanto pido disculpas anticipadas si encuentran alguna falta ortográfica o alguna mala dicción en el contenido que vaya a estar liberando nuevos posts de cosas que me interesen.

Empiezo con toda esta idea de crear contenido en español debido a que si bien hay mucho contenido (tutoriales/herramientas/etcétera) en ingles explicando como realizar ciertas cosas o tips & tricks, hoy en día casi no hay contenido que pueda ser beneficioso para personas que no tengan muy buenas habilidades a la hora de leer/comprender contenido en ingles (que por cierto en esta carrera de desarrollador es algo que deberían de tener en sus metas a corto plazo). Por lo tanto espero que les agrade la información que vaya posteando por aquí.

By the way, se habla English too, so if you have any question regarding the topic just let me know in the comments section.

## Stack

Las herramientas/gemas que serán necesarias para crear esta aplicación serán las siguientes:
* omniauth-google-oauth2.
* omniauth (en caso de tener algun error).
* omniauth-rails_csrf_protection
* devise
* postgresql instalado en nuestro equipo.
* ruby 3.0.
* rails 6 o mayor.
* Google API keys.

## Configurando Google API keys

* Deberemos de tener una cuenta de google cloud platform, una vez teniendo nuestra cuenta creada procederemos a dirigirnos a nuestro dashboard principal. En donde una vez situado sobre este procederemos a abrir el panel que se encuentra a la izquierda (el icono de hamburguesa) y hacer clic en la opción de **API y Servicios.**

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mf6hdd6i379chzk6xefv.png)

* Una vez situado sobre nuestro dashboard de API y Servicios procederemos a hacer clic en la opción de credenciales que se encuentra del lado izquierdo de nuestra pantalla. Posteriormente haremos clic en crear credenciales y en la opción de ID de cliente de OAuth.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slloms19ra6jhobrewxb.png)

* Al momento de seleccionar esta opción deberemos de escoger un tipo de aplicación entre el listado que muestra el dropdown, que en este caso sera **Aplicación Web**. Hecho esto deberemos de agregar un nombre de nuestro cliente Omniauth el cual en este caso sera **Omniauth Integration**.
Ahora es momento de situarnos en las opciones de **orígenes autorizados de javascript - URI de re direccionamiento autorizados**, los cuales deberán de contener lo siguiente:

    - Orígenes autorizados de javascript: [http://localhost:3000](http://localhost:3000/)
    - URI de re-direccionamiento autorizados:[http://localhost:3000/users/auth/google_oauth2/callback](http://localhost:3000/users/auth/google_oauth2/callback)

Acto seguido solo deberemos de hacer clic en crear.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sv173fk02eyynywl02s2.png)

* Realizado el punto anterior inmediatamente seremos redirigidos a nuestro dashboard de API y Servicios con una ventana emergente la cual nos brindara nuestro client id y nuestro secret key los cuales son datos que deberemos de almacenar en nuestro equipo o bien descargar el archivo JSON que se nos proporciona para tener resguardada esta información que deberá ser integrada posteriormente en nuestra aplicación.

## Creando La Aplicación.

* Empezaremos por crear la aplicación con el siguiente comando.
```ruby
rails new OmniauthExample --database=postgresql
```
Lo que realizara este comando es crear nuestra aplicación con la base de datos postgresql, por lo que no deberías de tener problemas siempre y cuanto tengas instalado/configurado postgresql de manera correcta. En caso de no tenerlo lo puedes realizar sin problema con el gestor de base de datos por defecto en Rails (Sqlite).

* Empezaremos por abrir nuestro proyecto OmniauthExample y posteriormente instalaremos las siguientes gemas.
```ruby
gem 'devise'
gem 'omniauth-google-oauth2'
gem "omniauth-rails_csrf_protection"
```
Ejecutaremos **bundle install** para la instalación de las gemas en nuestro proyecto y con esto estaremos terminando la configuración de nuestra aplicación.

* Una vez instaladas nuestras gemas de manera correcta, procederemos a configurar devise por medio de los siguientes comandos en consola.
```ruby
rails generate devise:install
```
En caso de querer generar las vistas por defecto de devise (que en este caso no lo haremos) y poder modificarlas en un futuro con nuestro propio estilo podemos ejecutar el siguiente comando en consola.
```ruby
rails generate devise:views
```

* Posteriormente procederemos a crear nuestro modelo User o Admin (ustedes deciden el nombre) en este caso sera User.
```ruby
rails generate devise User
```
Esto nos creara algunas cosas a tomar en cuenta como es el caso de el modelo user, la tabla users y una linea en nuestro archivo routes.rb el cual nos brindara todas nuestras acciones REST para nuestro modelo user.
```
devise_for :users
```

* Dentro de este mismo archivo llamado config.rb procederemos a realizar algunos cambios los cuales serán explicados enseguida.
```
Rails.application.routes.draw do
  root to: 'home#index'
  devise_for :users, controllers: {
                                    omniauth_callbacks: 'users/omniauth_callbacks',
                                    sessions: 'users/sessions'
                                  }
  devise_scope :user do
    get 'sign_out' => 'devise/sessions#destroy'
  end
end
```
* la linea de **root**  indica que tendremos una ruta raiz (la vista principal) por medio del controlador home y la accion index.
* devise_scope :user... esto lo creamos para tener un poco mas de personalizacion, de esta manera devise utiliza el scope user cuando accedemos a sign_out.
* devise_for :users... indica que tendremos el controlador omniauth_callbacks y el controlador sessions dentro de un directorio users en controllers, es decir.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t701743hvgtb8dw1shnz.png)

* Vamos a crear un poco de código boiler plate con cada una de las clases de la siguiente manera.
```ruby
# OmniauthController
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
# SessionsController
class Users::SessionsController < Devise::SessionsController
  def after_sign_out_path_for(_resource_or_scope)
    new_user_session_path
  end

  def after_sign_in_path_for(resource_or_scope)
    stored_location_for(resource_or_scope) || root_path
  end
end
# HomeController
class HomeController < ApplicationController
  def index
  end
end
```

* Acto seguido deberemos de agregar en nuestro modelo user en la sección de devise modules la característica para que la aplicación funcione con Omniauth de la siguiente manera.
```ruby
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: %i[google_oauth2]
```

* Por ultimo y no menos importante deberemos de crear las bases de datos en nuestro desarrollo local con el siguiente comando y ejecutar nuestras migraciones.
```ruby
# Creando las bases de datos
rails db:create
# Ejecutando migraciones
rails db:migrate
```

## Configurando nuestras keys en la aplicación.
* Para poder configurar nuestras keys del API de google contamos con dos enfoques (en este caso solo utilizaremos uno), el primero puede ser con una gema llamada dotenv en donde una vez instalada podremos agregar las llaves a un archivo con el nombre de .env sin ningún problema. Sin embargo Rails por defecto ya tiene un archivo de configuración para guardar nuestras llaves llamado credentials.yml.enc el cual es un archivo encriptado con todas nuestras variables de entorno necesarias para nuestra aplicación. Por lo tanto para hacer uso de este (en visual studio code) lo deberemos de abrir con el siguiente comando.
```ruby
EDITOR="code --wait" rails credentials:edit
```

* Una vez realizado lo anterior tendremos un pop-up el cual prácticamente le deberemos dar clic en ok y esto a su vez nos presentara el archivo credentials.yml.enc des-encriptando con lo cual podremos agregar nuestras keys de la siguiente manera y procederemos a salvar el archivo con sus modificaciones.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qbabn0vbuiyoaxgd3hq.png)
Con la finalidad de poder ver si nuestras credenciales fueron agregadas a nuestra aplicación solo deberemos de ejecutar el comando.
```ruby
rails credentials:show
```
El cual nos desplegara en terminal todas las keys que hayamos agregado a este archivo. En lo personal a mi me gusta mas este approach ya que así nos evitamos de depender de dependencias externas a nuestro proyecto y se lo dejamos ya directamente a Rails para que lo maneje.

* El ultimo paso de configuración que deberemos de hacer es directamente en el archivo devise.rb en la linea en donde aparezca la opción Omniauth (entre 270 - 280) y dentro de este agregaremos lo siguiente.
```ruby
config.omniauth :google_oauth2, Rails.application.credentials.google[:google_id_client], Rails.application.credentials.google[:google_secret_key], access_type: "online"
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vopivt1vwjl56bs23q4b.png)

## Adaptando la funcionalidad a nuestra aplicación.
* Hasta este momento si corremos nuestro servidor local tendremos un detalle debido a que no hemos creado ninguna vista para desplegar información, algo muy parecido a esto.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/colj07bm4ulsdgmu9zpx.png)
Por lo tanto lo que debemos de hacer es crear nuestra vista index dentro del home en nuestro directorio views, es decir.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9vjs2m69jlqtu6ztqbv.png)

* Posteriormente deberemos de agregar algo de contenido a esta vista index, por lo que el codigo que agregaremos sera el siguiente.
```ruby
<h1>This is the index view of home controller</h1>
<%= link_to "Login With Google", user_google_oauth2_omniauth_authorize_path, method: :post %>
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akwjzjtmva9rqhsxwo6o.png)

* Hasta ahora todo va a marchar bien sin embargo si hacemos click en el vinculo Login With Google tendremos como resultado lo siguiente en nuestro pantalla.

Esto se va a deber a que hasta el momento no hemos integrado una llamada a omniauth desde nuestra aplicación, por lo que vamos a tener que trabajar con algunas cosas que enseguida se listan.

- Crear una miagracion para agregar los campos provider - uid dentro de nuestra tabla users.
- Crear un método dentro del modelo user para que se retorne el usuario si ya fue creado en nuestra base de datos o si no en su defecto crearlo.
- Crear la lógica para el controlador omniauth_callback.

* Empezaremos por crear nuestra migración para agregar los campos.
```ruby
rails generate migration addFieldsToUser provider:string uid:string
```
Esto nos creara un archivo de migración, por lo cual debemos de ejecutar nuestras migraciones con rails db:migrate
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjoumufc48k06b07he7s.png)

* Enseguida agregaremos el siguiente código a nuestro modelo user.rb
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zgk1v3c3pnxko8aoet9b.png)

Prácticamente lo que estamos haciendo aquí es retornar un usuario en base en su correo en la linea 9. En caso de no encontrarlo lo va a crear en nuestra base de datos y posteriormente retornarlo.

* Posteriormente deberemos de crear la lógica para nuestro controlador omniauth de la siguiente manera.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhp0q1whhxu1foe78ohq.png)

Realizado esto reiniciaremos nuestro servidor solo con fines de empezar con todo el proceso desde el principio y nos ubicaremos en nuestra ruta principal localhost:3000. En donde al intentar hacer clic en nuestro vinculo "Login With Google" podremos ver que tendremos integrada de manera correcta la funcionalidad de Omniauth.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llez3l4f0x7k9ery8mr8.png)

* Por ultimo para atar cabos sueltos y poder visualizar que en efecto hicimos login, vamos a crear una nueva ruta en routes.rb con lo que al final nuestro archivo deberá de quedar así.
```ruby
Rails.application.routes.draw do
  root to: 'home#index'
  # Esta es la nueva ruta que agregamos
  get 'omniauth_test', to: 'home#display_omniauth'

  devise_for :users, controllers: {
                                    omniauth_callbacks: 'users/omniauth_callbacks',
                                    sessions: 'users/sessions'
                                  }
  devise_scope :user do
    get 'sign_out' => 'devise/sessions#destroy'
  end
end
```
Por lo que enseguida deberemos de crear dentro de views/home el archivo display_omniaut.rb con una simple etiqueta html.

```html
<h1>You are logged in now with google.</h1>
```

Con esto por fin si intentamos hacer todo el flujo al final tendremos como resultado la siguiente vista.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmh9qxgowube5cragw1y.png)

## Recomendaciones
Ahora que ya tenemos la funcionalidad creada para nuestra aplicación, lo que desde mi punto de vista yo recomiendo es que realices lo siguiente.

- Crear un servicio para manejar las llamadas a Omniauth, de esta manera el código estará mas DRY. (esto lo puedes ver en el repo de la aplicación que acabamos de crear). Una buena guía para esto la agrego enseguida
[Ruby Service Objects](https://www.toptal.com/ruby-on-rails/rails-service-objects-tutorial).

Ademas cabe mencionar que con esto estamos respetando uno de los principios de SOLID el cual es Single Responsability en donde una clase solo tiene que tener una responsabilidad, por lo que como pista te dejo mover toda la funcionalidad de user.rb a el servicio que vayas a crear y llamarlo desde el omniauth_callbacks_controller de la siguiente manera.
```ruby
user = OmniauthService.call(request.env['omniauth.auth'])
```
- Crear todo el proceso de login/logout junto cons sus hooks en application controller para cuando el usuario desee acceder a cierta pagina se compruebe que haya iniciado sesion.
- Investigar acerca de como testear con rspec o minitest segun sea tu preferencia service objects y la integracion de omniauth.

### Repo.
* [Omniauth Example](https://github.com/Daniel-Penaloza/OmniauthExample).


### Fuentes
* [Service Objects](https://www.toptal.com/ruby-on-rails/rails-service-objects-tutorial).
* [A good post about omniauth](https://medium.com/@sergiocortessatizabal/a-simple-way-to-set-devise-with-omniauth2-0-facebook-google-617a88999d8e)