Autenticación de Usuarios en Controladores Rails

Clase 23 de 33Curso de Creación de APIs con Ruby on Rails

Resumen

Implementa autenticación en Rails 5 con seguridad y claridad: usa before_action, valida el header Authorization con regex, resuelve el usuario por token y comparte el contexto con Current. Con esto, proteges create y update, y controlas el acceso en show a posts públicos y borradores del usuario autenticado.

¿Cómo asegurar create y update con before_action?

Para proteger las acciones sensibles, se configura un before_action que ejecute authenticate_user! solo antes de create y update. Así, cada request que pueda modificar datos verifica primero que exista un usuario válido.

¿Qué hace authenticate_user! en el controlador?

  • Lee el header de autenticación del request.
  • Verifica que exista el header Authorization.
  • Valida el formato del header: "bitter" seguido del token.
  • Extrae el token con un grupo de captura del regex.
  • Busca el usuario con ese token y lo asigna a Current.user.
  • Si no hay usuario válido, hace render con error y status unauthorized.

Ejemplo en Ruby:

# En PostsController
before_action :authenticate_user!, only: [:create, :update]

private

def authenticate_user!
  auth = request.headers['Authorization']
  regex = /\Abitter\s+(.+)\z/

  if auth && (m = auth.match(regex))
    token = m[1]
    if user = User.find_by_auth_token(token)
      Current.user = user
      return
    end
  end

  render json: { error: 'Unauthorized' }, status: :unauthorized
end

¿Cómo se valida el header Authorization con regex?

  • Se define un regex con un grupo de captura: bitter seguido del token.
  • El grupo de captura se indica con paréntesis.
  • Con match data: m[0] es el string completo; m[1] es el token.

Ejemplo rápido en consola de Rails:

t = 'bitter 123token'; regex = /\Abitter\s+(.+)\z/; m = t.match(regex); m[0]; m[1]

Claves prácticas: - Usa asignación dentro del if: if user = User.find_by_auth_token(token). - En Ruby, nil es falsy y cualquier otro objeto es truthy. - Si no hay match o usuario, responde con status: :unauthorized y error "Unauthorized".

¿Cómo extraer el token y resolver el usuario?

La extracción del token depende de un grupo de captura en el regex. Con m = auth.match(regex), m[1] contiene el valor del token. Luego, se resuelve el usuario con User.find_by_auth_token(token).

¿Cómo funciona truthy/falsy en el if de asignación?

  • if user = User.find_by_auth_token(token) asigna y evalúa.
  • Si hay usuario: entra al if y continúa el flujo normal.
  • Si no hay usuario (es nil): se salta el if y se hace render unauthorized.

¿Qué devuelve el controlador si falla la autenticación?

  • Un JSON con el error.
  • Un status unauthorized.

Ejemplo:

render json: { error: 'Unauthorized' }, status: :unauthorized

¿Cómo compartir current.user en toda la solicitud?

Para acceder al usuario autenticado en diferentes partes, se usa Current, basado en current attributes de Rails 5. Esto guarda el contexto de la sesión actual y lo hace accesible en controladores y modelos.

¿Cómo definir y usar Current en Rails 5?

  • Se crea la clase Current que hereda de ActiveSupport::CurrentAttributes.
  • Se declara el atributo user.
  • Se asigna en authenticate_user!: Current.user = user.

Código:

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user
end

Luego se puede usar Current.user en cualquier punto del ciclo de vida del request.

¿Qué reglas aplicar en show para posts públicos y borrador?

  • Permitir ver posts públicos sin autenticación.
  • Permitir ver borradores solo si el usuario está autenticado y es el dueño del post.
  • Si no cumple, responder con unauthorized.

Sugerencia de implementación en show:

# Regla: permitir posts públicos; si es borrador, solo si pertenece al usuario autenticado.
# En caso contrario, responder unauthorized.

Ideas clave a retener: - before_action con only: [:create, :update] para rutas sensibles. - Header Authorization con formato "bitter " validado con regex y grupos de captura. - Uso de match data: m[0] para el match completo y m[1] para el token. - If con asignación y semántica truthy/falsy de Ruby para flujos claros. - render con error y status unauthorized cuando no hay autenticación. - Current.user con current attributes para compartir el contexto del usuario.

¿Te gustaría ver un ejemplo completo integrando create, update y la regla de show con Current.user? Cuéntame en qué parte quieres profundizar o pega tu controlador para revisarlo juntos.