diff --git a/article_scraper/resources/tests/thumbnails/a-chacon.html b/article_scraper/resources/tests/thumbnails/a-chacon.html new file mode 100644 index 0000000..3e1bc5e --- /dev/null +++ b/article_scraper/resources/tests/thumbnails/a-chacon.html @@ -0,0 +1,808 @@ + + + +
+ + + + + + + +Andrés
+•
+16 October 2024
+•
++ + 6 mins
+Como ya saben, una de las funcionalidades nuevas de Rails 8 es el nuevo generador básico de autenticación que viene a demostrar que no es tan complejo desarrollar todo lo que respecta a autenticación en una aplicación con Rails y que muchas veces no es necesario depender de terceros (gemas). La discusión comenzó aquí.
+ +Dicho esto, veamos que pasa usando el generador en una aplicación API-Only:
+ + rails -v
+Rails 8.0.0.beta1
+
rails new app --api & cd app
+
Y ejecutamos el nuevo comando:
+ + rails g authentication
+ create app/models/session.rb
+ create app/models/user.rb
+ create app/models/current.rb
+ create app/controllers/sessions_controller.rb
+ create app/controllers/concerns/authentication.rb
+ create app/controllers/passwords_controller.rb
+ create app/mailers/passwords_mailer.rb
+ create app/views/passwords_mailer/reset.html.erb
+ create app/views/passwords_mailer/reset.text.erb
+ create test/mailers/previews/passwords_mailer_preview.rb
+ gsub app/controllers/application_controller.rb
+ route resources :passwords, param: :token
+ route resource :session
+ gsub Gemfile
+ bundle install --quiet
+ generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
+ rails generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
+ invoke active_record
+ create db/migrate/20241016002139_create_users.rb
+ generate migration CreateSessions user:references ip_address:string user_agent:string --force
+ rails generate migration CreateSessions user:references ip_address:string user_agent:string --force
+ invoke active_record
+ create db/migrate/20241016002140_create_sessions.rb
+
Ok, ahora por ejemplo, si revisamos SessionsController
veremos que el método de Login
se ve de la siguiente forma:
def create
+ if user = User.authenticate_by(params.permit(:email_address, :password))
+ start_new_session_for user
+ redirect_to after_authentication_url
+ else
+ redirect_to new_session_url, alert: "Try another email address or password."
+ end
+ end
+
O sea, redirecciona a rutas y/o vistas que en nuestra API no existen ni hacen sentido, y además si inspeccionamos el metodo start_new_session_for
nos daremos cuenta de que el sistema está basado 100% en autenticación mediante cookies. Entonces, ¿qué hacemos?
Mi propuesta es la siguiente: el generador crea las bases para la autenticación y creo que funciona bastante bien, por lo que con unas pequeñas modificaciones podemos dejar funcionando una autenticación Bearer (Token Authentication) rápidamente en nuestra API con Rails 8 más los archivos ya generados.
+ +El primer paso será agregar persistencia para nuestro token, para esto modificaremos la migración que crea las sessiones y agregaremos un nuevo campo llamado token
:
create_table :sessions do |t|
+ t.references :user, null: false, foreign_key: true
+ t.string :ip_address
+ t.string :user_agent
+ t.string :token # HERE
+
+ t.timestamps
+ end
+
Ahora simplemente ejecuta rails db:migrate
y create un usuario de prueba por consola, yo lo haré con esta línea User.create(email_address: "[email protected]", password: "123456789")
(Lo utilizaremos más tarde). Luego debemos crear un nuevo token para cada sesión nueva de un usuario, para esto lo más simple es usar un callback en el modelo Session
:
# app/models/sessions.rb
+class Session < ApplicationRecord
+ belongs_to :user
+ before_create :generate_token # Here call
+
+ private
+ def generate_token # Here implement, generate the token as you wish.
+ self.token = Digest::SHA1.hexdigest([ Time.now, rand ].join)
+ end
+end
+
Ahora volviendo al metodo start_new_session_for
en el concern Authentication
, no es necesario que creemos una cookie, asi que debemos remover esa linea y dejar el metodo de la siguiente forma:
# app/controllers/concerns/authentication.rb
+def start_new_session_for(user)
+ user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
+ Current.session = session
+ end
+end
+
Y modificaremos el create
de SessionsController
para que las respuestas sean en formato json y no redirecciones:
# app/controllers/sessions_controller.rb
+def create
+ if user = User.authenticate_by(params.permit(:email_address, :password))
+ start_new_session_for user
+ render json: { data: { token: Current.session.token } }
+ else
+ render json: {}, status: :unauthorized
+ end
+end
+
Para hacer que todo esto funcione debemos hacer dos cosas:
+ +Incluir el modulo Authentication
en ApplicationController
:
# app/controllers/application_controller.rb
+class ApplicationController < ActionController::API
+ include Authentication
+end
+
Eliminar la linea numero 6 de este mismo concern:
+ +# app/controllers/concerns/authentication.rb
+ included do
+ before_action :require_authentication
+ helper_method :authenticated? # This, we don't use helpers in APIs
+ end
+
Hasta este punto ya deberíamos tener el login funcionando. Para probar esto voy a agregar OasRails, que a propósito ya está funcionando con Rails 8 y voy a enviar un par de peticiones a ver como se comporta, no explicaré como implementar OasRails, para eso puedes ver el repositorio o leer más en este post.
+ +Inicio de sesión exitoso:
+ +Inicio de sesión fallido:
+ +Ya podemos generar tokens, ahora modificaremos el código para autenticarnos con ese mismo token. Para eso, cambiaremos la lógica de buscar la sesión actual del usuario con base en la cookie a buscarla basándonos en la cabecera Authorization
:
+# app/controllers/concerns/authentication.rb
+ def resume_session
+ Current.session = find_session_by_token
+ end
+
+ def find_session_by_cookie
+ Session.find_by(token: request.headers[:authorization]&.split(" ")[-1])
+ end
+
Para probar esto creo que tendremos que hacer rápidamente un modelo que dependa de User
y que requiera autenticación para utilizar. Intentemos con rails g scaffold project title:string description:text user:references
y le agregamos al principio del controlador la línea de código before_action :require_authentication
.
Aquí les dejo una pequeña prueba del index de Projects autenticado con el token que obtuve en las pruebas anteriores:
+ +Con esto ya tienes gran parte de la lógica de autenticación funcionando en la aplicación API-Only. Te queda continuar con las modificaciones en el resto de los endpoints para que las respuestas sean en formato json y no supuestas vistas que no existen en la aplicación.
+ +Probablemente de aquí a que se lance la versión final de Rails 8 aparezca un PR solucionando esto y el generador funcione correctamente en modo API-Only. Hasta entonces, con estas pequeñas modificaciones ya puedes seguir construyendo tu API.
+ ++ ¿Te gustó? ¡Compártelo! +
+ + +