Ruby on Rails: Implementando Autenticação de Usuário

No Rails existem duas formas de fazer a autenticação do usuário: você pode escrever todo o código ou pode utilizar a gem Devise. Nesse artigo serão mostradas as duas maneiras.

Neste artigo vamos criar um sistema de autenticação usando o Ruby. Para começar o projeto, localize a pasta ele onde ficará salvo pelo seu prompt (utilize o do comando cd nomedapasta ou cd caminho/para/pasta). Em seguida digite rails new site.

Com isso, todos os arquivos padrões do Rails foram criados. A partir de agora, é preciso desenvolver a criação de usuário e em seguida, sua autenticação para acesso ao sistema.

Criação de Usuário e senha encriptada

Acesse o console dentro da pasta site e escreva o seguinte comando:

rails generate controller Users new

Nesse momento, o Rails cria a rota (route) e a view para a ação new, requisitada durante a criação do controller, também conhecida como sign up.

Agora, para gerar o modelo use o comando a seguir:

rails generate model User name:string email:string

Ao ser gerado, estamos criando seu registro, através de uma migration. Porém, este registro ainda não foi salvo e é preciso que rodar o comando rake para efetivá-lo no sistema, como mostrado a seguir:

rake db:migrate

É possível checar a criação da tabela no banco dentro da pasta db/ no arquivo schema, onde ficarão todas as tabelas e seus atributos do sistema. Para se inscrever no sistema é preciso o campo senha. Então, você deve estar se perguntando por que o campo não foi criado? Mas para fazer de uma forma segura, a melhor opção é fazer um hashed password.

O sistema salvará a senha apenas como uma sequência aleatória de letras e números. O Rails possui um método específico para essa ação. Para isso, entre no modelo de usuário app/models/user.rb e digite o seguinte comando:

class User < ActiveRecord::Base has_secure_password end

Quando incluído no código, será possível salvar:

O único requerimento para funcionamento desse comportamento é a criação do campo password_digest na tabela User. Para implementar é preciso criar uma migration com o código a seguir:

rails generate migration add_password_digest_to_users password_digest:string

Para aplicar o novo campo, digite no prompt:

rake db:migrate

Porém, o has_secure_password utiliza uma função hash chamada bcrypt. Desta forma, é garantido que, mesmo se um hacker obtenha uma cópia do banco, não será capaz de logar no sistema. Então, entre na sua Gemfile com o código a seguir:

gem 'bcrypt'

Logo após, digite o comando bundle install para sua instalação.

Validação de Usuário

O sistema ainda está permitindo que o usuário cadastre-se até mesmo com o campo e-mail vazio. No entanto, isso obviamente não pode ocorrer, pois é preciso validar os campos nome, e-mail e password. Para resolver isso, entre no modelo User e faça as seguintes mudanças (app/models/user.rb):

class User < ActiveRecord::Base has_secure_password validates name, presence: true, length: {maximum: 50} validates password, presence: true, length: {minimum: 6} VALID_EMAIL_FORMAT= /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i validates email, presence: true, length: {maximum: 260}, format: { with: VALID_EMAIL_FORMAT}, uniqueness: {case_sensitive: false} before_save { self.email = email.downcase } end

Os campos nome e password estão sendo obrigatoriamente requisitados para o preenchimento no futuro formulário, com um tamanho máximo e mínimo de caracteres.

O campo email é um pouco diferente, pois poderia ocorrer do usuário digitar o email com vírgula, ou sem @, ou outro quesito inválido. Portanto, é preciso um formato específico desse campo, ou seja, é atribuída uma variável estática o formato padrão Rails.

É necessário que o email seja único no sistema, afinal, duas contas com o mesmo e-mail daria conflito, ou seja, é atribuído o comportamento uniqueness. Mas, para que um email com letra maiúscula não seja salvo diferentemente dele mesmo em letra minúscula, o campo case_sensitive é false e antes de salvar esse campo ele será transformado em letra minúscula.

Por fim, existe um problema: Quando o usuário futuramente for logar no sistema, é necessário checar se o email corresponde ao salvo no banco. Portanto, o interpretador lerá todas as linhas de registro de usuário ATÉ encontrar o correspondente. Esse comportamento pode comprometer o funcionamento do sistema. Então, a melhor opção é utilizar o index na coluna de email através da migration:

rails generate migration add_index_to_users_email

A partir de agora, todas as ocorrências de email serão salvas como index e o sistema procurará diretamente nos registros de e-mail. Termine salvando no banco:

rake db:migrate

User Controller, View e Route

Agora que a validação de preenchimento dos campos e os requisitos de senha estão feitos, é preciso criar as ações responsáveis pela criação de usuário e o seu formulário.

Para isso, entre em app/controllers/users_controller.rb e digite o código:

class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) if @user.save redirect_to @user, notice: "Usuário foi criado com sucesso!" #tire o método de comentário quando criar o helper. #Usuário depois de cadastrar-se acessa o sistema automaticamente #sign_in(@user) else render action: :new end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end

Como foi possível perceber, duas actions foram criadas:

A única parte, talvez incomum, seja o método user_params, pois no Rails 4, começou a ser implementado o Strong parameters, onde são especificados os parâmetros requeridos e permitidos, evitando a atribuição em massa.

Por fim, falta a implementação do formulário para registro de usuário. Para isso, entre em app/views/users/new.html.erb e use o código da Listagem 1.

Listagem 1. Formulário do usuário

<%= form_for @user do |f| %> <% if @user.errors.any? %> <div id="error_explanation"> <div class="alert-error"> O formulário contém <%= pluralize(@user.errors.count, "erro") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :email %> <%= f.email_field :email %> </div> <div class="field"> <%= f.label :password %> <%= f.password_field :password %> </div> <div class="field"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation %> </div> <div class="actions"> <%= f.submit "Cadastrar"%> </div> <% end %>

Na primeira parte foi utilizado o helper form_for que cria o formulário. Em seguida, os erros, caso ocorra o preenchimento incorreto, são programados para aparecer. Por fim, são inseridos os campos nome, email, password, password_confirmation e o botão para cadastrar-se. Existem campos helpers como email_field e password_field que já possuem certas validações.

Por fim, para definir a URL é preciso entrar no arquivo routes.rb e, com o helper do Rails, todas ações padrões são mapeadas. Então, entre em config/routes.rb e use o código a seguir:

Rails.application.routes.draw do resources :users end

Dê início ao servidor com Rails server e acesse a página http://localhost:3000/users/new para ver o formulário. Não se assuste se, ao clicar em cadastrar, pegar um erro como “The action show could not be found in UsersController”. Isso ocorrer porque falta a action show e sua view no sistema.

Login da Sessão

Agora que é possível inscrever-se no sistema, é preciso permitir acessá-lo. Porém, o HTTP trata cada requisição como independente, ou seja, não lembra a informação ao mudar de página. Então, é preciso usar uma sessão (session), que é uma conexão semipermanente entre dois computadores.

A técnica mais comum é a utilização de cookies. Eles são capazes de persistir de uma página para outra. Nesse artigo será utilizado o método do Rails conhecido como session e que acaba ao fechar o browser.

No login será renderizada a ação new e para entrar no sistema, o create será utilizado. Ao fazer logout, a sessão será destruída, ou seja, o método destroy entra em ação. Mas, para criá-las, precisamos do SessionsController. Então, vá ao prompt ou terminal e determine:

rails generate controller Sessions new

Incluindo a ação new, cria-se uma view com seu nome (o que não será preciso para as ações create e destroy). Nesse momento, falta apenas definir as rotas do sistema.

Entre em config/routes.rb e use a Listagem 2.

Listagem 2. Rotas do sistema

Rails.application.routes.draw do resources :users get 'sign_in' => 'sessions#new' post 'sign_in' => 'sessions#create' delete 'sign_out' => 'sessions#destroy' end

No momento, se o servidor estiver rodando, basta acessar a página http://localhost:3000/sign_in e será mostrado no browser esse HTML:

<h1>Sessions#new</h1> <p>Find me in app/views/sessions/new.html.erb</p>

Este aparece porque ainda não foi criado o formulário new. Portanto, não é possível fazer o login. Então, entre em app/views/sessions/new.html.erb e digite a Listagem 3.

Listagem 3. Formulário New

<%= form_for :session, url: sign_in_path do |f| %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true %> </div> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password, autocomplete: "off" %> </div> <div class="actions"> <%= f.submit "Log in" %> </div> <p>New user? <%= link_to "Sign up now!", new_user_path %></p> <% end %>

Olhando a estrutura, recordamos muito o formulário de Sign Up, mas a principal diferença é que no new.html.erb do Session não utiliza a analogia @user porque não existe modelo Session. Ele é apenas um controller que tem a função de permitir o login de usuário, ou seja, ele não é o usuário. Portanto, é dado o caminho/url e o método onde este template será mostrado.

Agora, falta o preenchimento do controller. Primeiramente, percebe-se que os parâmetros recebidos ao criar a sessão serão email e senha de usuário. Então, o primeiro passo é saber se os dados informados correspondem com os registrados no banco de usuário. Isso significa que dentro da action create os parâmetros tem que ser autenticados.

É preciso encontrar o email do usuário e autenticar a senha da sua entrada. Parece difícil, mas a biblioteca Active Record disponibiliza o método User.find_by ,e o has_secure_password providencia o método authenticate. Ou seja, a lógica fica igual à Listagem 4.

Listagem 4. Autenticação de senha

@user = User.find_by(email: params[:session][:email].downcase) if @user && user.authenticate(params[:session][:password]) sign_in @user end

Por enquanto, nessa lógica o código verifica o email (transforma-o em letra minúscula) e a senha, passados como parâmetros, e utiliza um método sign_in, que ainda não existe no projeto. Esse método terá o código necessário para gravar a sessão.

Implementar sessões vai envolver um grande número de funções e elas serão utilizadas por muitos controllers e views. Então, será usado um module helper que é fornecido pelo Ruby, através do caminho app/helpers/sessions_helper.rb, digite o comando da Listagem 5.

Listagem 5. Login

module SessionsHelper def sign_in session[:user_id] = @user.id end end

A partir de agora, através desse método o usuário está logando com um cookie temporário que expira ao fechar o browser. Mas, para chamar esse método em todos os controllers é preciso incluir o helper no ApplicationController. Acesse app/controllers/application_controller.rb e digite o comando da Listagem 6.

Listagem 6. Controller da seção

class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception include SessionsHelper end

O controller de Sessions será finalmente preenchido. Modifique-o em app/controllers/sessions_controller.rb usando o código da Listagem 7.

Listagem 7. Modificação da session

class SessionsController < ApplicationController before_action :block_access, except: [:destroy] def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) sign_in(@user) redirect_to @user else render 'new' end end end

O método block_access será criado, afinal, quando o usuário já está logado não deve ser permitido fazer login novamente.

Se o acesso for permitido, a sessão é criada e gravada e o usuário é redirecionado para sua página de perfil, ainda não existente no sistema.

Para checar se existe um usuário logado cria-se o current_user. Com esse método será possível retornar as informações do usuário logado no sistema, inclusive em controllers, views e modelos. Mas, para criar esse helper precisa-se checar se existe alguma sessão. Então, o código da Listagem 8 fica assim em app/helpers/sessions_helper.rb.

Listagem 8. SessionsHelper

module SessionsHelper def sign_in session[:user_id] = @user.id end def current_user @current_user ||= User.find_by(id: session[:user_id]) end def block_access if current_user.present? redirect_to users_path end end end

Agora, é possível checar seu status de login, ou seja, se um usuário está logado o current_user não é nil. Então vamos criar um método para isso também. Continue no app/helpers/sessions_helper.rb e insira o código da Listagem 9.

Listagem 9. Método de checagem.

module SessionsHelper ... def logged_in? !current_user.nil? end end

Nas views podem-se criar validações de acesso, assim como nos controllers, para apenas quem está logado ter acesso ao sistema, mas veremo-las a seguir.

Logout da Sessão

Logout envolve desfazer o efeito do login e seus métodos no sistema. Quando entramos no projeto como current_user utilizamos o método create. Então, terminar a sessão é deletar o registro, ou seja, fazer o current_user ser nil. Assim como criamos um método sign_in, será criado um sign_out. Para isso, entre em app/helpers/sessions_helper.rb e insira o código da Listagem 10.

Listagem 10. Logout

module SessionsHelper ... def sign_out session.delete(:user_id) @current_user = nil end end

Com o novo método, só é necessário utilizá-lo no SessionsController, por isso acesse app/controllers/sessions_controller.rb e insira o código da Listagem 11.

Listagem 11. Utilizando o logout no sessionsController

class SessionsController < ApplicationController def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) sign_in(@user) redirect_to current_user else render action: :new end end def destroy sign_out redirect_to root_url end end

O usuário tem como sair do sistema, mas não tem onde clicar para fazer essa ação, além de estar sendo redirecionado para a página principal que não foi definida ainda no projeto. Portanto, vá a config/routes.rb e insira o código da Listagem 12.

Listagem 12. Comando para redirecionamento

Rails.application.routes.draw do ...... root 'sessions#new' end

Para entrar no sistema agora, basta acessar http://localhost:3000/. Mas ainda não usamos o sign_out. Então, vamos criar uma header para a aplicação, assim, quando estiver logado, alguns links aparecerão. Acesse app/views/layouts e crie um arquivo _header.html.erb, como mostra a Listagem 13.

Listagem 13. Arquivo header

<header> <div> <nav> <ul> <% if logged_in? %> <li><%= link_to "Users", users_path %></li> <li> <ul> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", '#' %></li> <li> <%= link_to "Log out", sign_out_path, method: "delete" %> </li> </ul> </li> <% else %> <li><%= link_to "Log in", root_path %></li> <% end %> </ul> </nav> </div> </header>

Quando o usuário estiver na sessão, ele terá opções de clicar para ver todos os usuários, seu perfil (show), configurações (ainda não criado) e fazer logout do sistema. Se não existir current_user, apenas constará a opção de login. Como o arquivo foi criado, mas ainda não está sendo chamado no projeto, precisamos renderizá-lo na application view. Afinal, estes links serão utilizados em todas as páginas do sistema. Então, acesse app/views/layouts/application.html.erb e insira o código da Listagem 14.

Listagem 14. Inserindo links na página

.... <body> <%= render 'layouts/header' %> <%= yield %> </body>

Para checar melhor, foi criada a ação index. Assim, serão mostrados todos os usuários do sistema (as opções caso logado) e também a action show que vamos criar agora. Para isso, acesse app/controllers/users_controller.rb e insira o código da Listagem 15.

Listagem 15. Action Show

class UsersController < ApplicationController def index @users = User.all end def show @user = User.find(params[:id]) end end

Em seguida, crie sua view em app/views/users chamada index.html.erb, como mostra a Listagem 16.

Listagem 16. Index.html

<h1> Users </h1> <table> <thead> <tr> <th>Name</th> <th>Email</th> <th></th> </tr> </thead> <tbody> <% @users.each do |user| %> <tr> <td><%= user.name %></td> <td><%= user.email %></td> <td><%= link_to "Show", user %></td> </tr> <%end%> </tbody> </table>

Por último, crie também a view show na mesma pasta users, com o nome da action show.html.erb, como mostra a Listagem 17.

Listagem 17. View da action show

<html> <body> <h3>Perfil de <%= @user.name %> </h3> <%= @user.email %> </body> </html>

Pronto! Após logar e clicar em “Users”, a tela ficará como mostra a Figura 1.

Figura 1. Todos os usuários do sistema e as opções de um current_user.

Autorização do Usuário

Apesar do Logout funcionar, existe um problema: qualquer usuário pode acessar as páginas do sistema, e obviamente, isso não pode ocorrer.

No contexto de aplicações web, a autenticação nos permite identificar os usuários do nosso site e a autorização nos permite controlar o que eles podem fazer. Agora estamos em condições de implementar a autorização.

No futuro, pode ser necessário que o usuário esteja logado para determinados acessos. Portanto, será criado um método na ApplicationsController que poderá ser usado em outros controllers, como mostra a Listagem 18.

Listagem 18. ApplicationController

class ApplicationController < ActionController::Base ..... def authorize unless logged_in? redirect_to root_url end end end

Temos o método criado, porém não implementado. No Rails, utiliza-se o before_action para cada ação checada e validada de acordo com o método utilizado. Então acesse app/controllers/users_controller.rb e insira o código da Listagem 19.

Listagem 19. Before_action

class UsersController < ApplicationController before_action :authorize, except: [:new, :create] ..... end

O except foi utilizado porque se deve permitir que o usuário inscreva-se no sistema, que é renderizado pelo new e efetivado pelo create.

Edit, Update e Destroy

A autenticação do usuário está pronta, mas ainda estão faltando algumas das REST actions da tabela User, no caso, edit, update e destroy, incluindo as limitações para fazê-las. Apenas os próprios usuários serão capazes de atualizar ou deletar sua conta. Então, vamos ao controller para criar as actions, indo em app/controllers/users_controller.rb, como mostra a Listagem 20.

Listagem 20. Atualização e exclusão de conta

class UsersController < ApplicationController ... def edit @user = User.find(params[:id]) end def update @user = User.find(params[:id]) if @user.update_attributes(user_params) redirect_to users_path else render action: :edit end end def destroy @user = User.find(params[:id]) @user.destroy sign_out redirect_to root_path end ... end

O usuário será encontrado com a action edit e um formulário (que costuma ser igual ao de criação) irá aparecer. Em seguida, quando forem terminadas as mudanças e o botão de submit for clicado, a action update checará se os parâmetros permitidos foram atualizados e irá redirecionar para a página index com os novos dados. Na action destroy, o usuário é encontrado e apagado e sua sessão também. Em seguida, é direcionado para a página de login, que é a principal do projeto.

Porém, existem alguns fatores faltando: o usuário só pode fazer essas ações caso a conta seja dele, mas a view de editar não foi criada.

Como o formulário de criação é o mesmo de edição crie uma template _form.html.erb na pasta app/views/users/ e copie o código de new.html.erb. O código do edit.html.erb será o mesmo. A partir de agora, as views ficam assim:

Dentro do arquivo >app/views/users/new.html.erb:

<h1>Sign Up</h1> <%= render 'form' %>

Dentro do arquivo >app/views/users/edit.html.erb:

<h1><%= User Settings %></h1> <%= render 'form' %>

Apenas o usuário terá acesso para mudar seu cadastro. Então, será criado um método e, como futuramente pode ser utilizado em outros controllers, ele ficará na application controller (app/controllers/application_controller.rb). Acesse esse arquivo e digite o código da Listagem 21.

Listagem 21. Mudança de cadastro

class ApplicationController < ActionController::Base ... def correct_user? @user = User.find(params[:id]) unless current_user == @user redirect_to users_path end end

Por fim, o projeto precisa de algumas mudanças, como adicionar links em algumas views e a before_action no controller de Users. Então, siga as alterações presentes a seguir:

Dentro do arquivo app/controllers/users_controller.rb:

class UsersController < ApplicationController .... before_action :correct_user?, only: [:edit, :update, :destroy] ... end

Dentro do arquivo app/views/users/show.html.erb:

<html> <body> <h3> Perfil de <%= @user.nome %> </h3> <%= @user.email %> <% if current_user == @user %> <ul> <li><%= link_to "Edit", edit_user_path(current_user) %></li> <li><%= link_to "Delete", @user, method: :delete%></li> </ul> <%end%> </body> </html>

Dentro do arquivo app/views/users/edit.html.erb:

... <ul> <li><%= link_to "Edit", edit_user_path(current_user) %></li> <li><%= link_to "Delete", @user, method: :delete%></li> </ul>

Dentro do arquivo >app/views/layouts/_header.html.erb:

<li><%= link_to "Settings", edit_user_path(current_user) %></li>

Autenticação do Usuário com o Devise

Devise é uma solução de autenticação popular para aplicações Rails.

Para instalá-lo faremos como com uma gem: acesse a Gemfile e salve com:

gem ‘devise’

Instale a gem no projeto usando o código a seguir no cmd ou terminal:

bundle install

A partir desse momento, precisa-se rodar o generator utilizando o seguinte código:

rails generate devise:install

Pronto! Todas as opções de configurações do Devise estão no projeto.

Implementando Usuário

Agora precisamos criar o registro de usuário e usaremos o Devise. Desta forma, todas as actions, views e validações serão criadas. Basta rodar o comando:

rails generate devise User

Se quiser que o usuário confirme o cadastro por e-mail, antes de ter acesso ao sistema, tire da forma comentário a opção ‘confirmable’ no modelo User. Também retire as que estão comentadas na migration da sessão como Confirmable. Em seguida, para salvar o registro de user no schema use o seguinte código:

rake db:migrate

O Devise utiliza o mailer, então é preciso determinar a porta nas configurações. Para isso entre em config/ environments/development.rb e use o código:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

Por fim, não há acesso as views do Devise, se não são solicitadas. Portanto dê o comando:

rails generate devise:views

Autorização do Usuário

O Devise disponibiliza inúmeros helpers, inclusive o current_user. Mas outro importante é o método responsável por autorizar acesso do usuário no sistema, que poderá ser utilizado em qualquer controller. Então, basta escrever em qual desejar:

before_action :authenticate_user!

Strong Parameters no Devise

O padrão de atributos permitidos no Devise são os campos email, senha e confirmação de senha. Então, quando o desenvolvedor quer permitir que o usuário crie, por exemplo, seu nome ou escolha um perfil de acesso para o sistema, é preciso configurar a permissão desses parâmetros. Para isso acesse app/controllers/application_controller.rb e use o código da Listagem 22.

Listagem 22. Acesso ao perfil

class ApplicationController < ActionController::Base .... before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up) << :name end end

A partir de agora, o Devise irá autorizar o registro de nome na criação do usuário (“:sign_up”), mas vale lembrar que é preciso configurar também caso seja permitido editar o nome, que utilizaria o “:account_update”.

Ainda falta criar o registro de “name” no banco, adicionar o campo na view e criar a validação no modelo. Portanto, no cmd ou terminal digite:

rails generate migration add_name_to_users name:string

Agora, seguido de rake db:migrate use as seguintes instruções:

Dentro do arquivo app/views/users/registrations/new.html.erb:

<div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div>

Dentro do arquivo app/models/user.rb:

class User < ActiveRecord::Base ... validates :name, presence: true ... End

Pronto, a Autenticação de usuário está completa. Rode o comando rails server e cadastre-se no seu próprio sistema http://localhost:3000/users/sign_up.

Olhando assim, parece que é muito, mas simples e que vale muito mais a pena.

Trabalhar com o Devise possui diversas questões complexas e é preciso determinado conhecimento no Framework Rails.

Espero que esse artigo tenha ajudado a compreender o funcionamento de uma autenticação com o Rails.

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados