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:
- Um atributo password_digest no banco, que será a senha encriptada;
- Os atributos virtuais, password e password_confirmation, incluindo a requisição se eles combinam;
- Um método authenticate que retorna o usuário quando a senha está correta.
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:
- New, que é responsável por iniciar o novo usuário;
- Create, que irá efetivá-lo no banco e redirecioná-lo para a sua página, que é a action show, ainda não criada.
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.