O que é autenticação baseada em token?

A autenticação baseada em token (também conhecida como autenticação JSON Web Token ) é uma nova maneira de lidar com a autenticação de usuários em aplicativos. É uma alternativa à autenticação baseada em sessão .

A diferença mais notável entre a autenticação baseada em sessão e a autenticação baseada em token é que a autenticação baseada em sessão depende muito do servidor. Um registro é criado para cada usuário conectado.

A autenticação baseada em token não tem estado - ela não armazena nada no servidor, mas cria um token codificado exclusivo que é verificado toda vez que uma solicitação é feita.

Ao contrário da autenticação baseada em sessão, uma abordagem de token não associa um usuário com informações de login, mas com um token exclusivo que é usado para transportar transações cliente-host. Muitos aplicativos, incluindo Facebook, Google e GitHub, usam a abordagem baseada em tokens.

Benefícios da autenticação baseada em token

Existem vários benefícios em usar essa abordagem:

Domínio cruzado / CORS

Cookies e CORS não combinam bem em domínios diferentes. Uma abordagem baseada em token permite que você faça chamadas AJAX para qualquer servidor, em qualquer domínio, porque você usa um cabeçalho HTTP para transmitir as informações do usuário.

Sem estado

Os tokens são sem estado. Não há necessidade de manter um armazenamento de sessão, pois o token é uma entidade independente que armazena todas as informações do usuário nele.

Dissociação

Você não está mais vinculado a um esquema de autenticação específico. Os tokens podem ser gerados em qualquer lugar, de modo que a API pode ser chamada de qualquer lugar com um único comando autenticado em vez de várias chamadas autenticadas.

Pronto para celular

Os cookies são um problema quando se trata de armazenar informações do usuário em aplicativos móveis nativos. A adoção de uma abordagem baseada em tokens simplifica significativamente esse processo de economia.

CSRF (Cross Site Request Forgery)

Como o aplicativo não depende de cookies para autenticação, ele é invulnerável a ataques de solicitação entre sites.

atuação

Em termos de carga do lado do servidor, uma viagem de ida e volta na rede (por exemplo, encontrar uma sessão em um banco de dados) provavelmente levará mais tempo do que calcular um código HMACSHA256 para validar um token e analisar seu conteúdo. Isso torna a autenticação baseada em token mais rápida do que a alternativa tradicional.

Como funciona a autenticação baseada em token?

A maneira como a autenticação baseada em tokens funciona é simples. O usuário insere suas credenciais e envia uma solicitação ao servidor. Se as credenciais estiverem corretas, o servidor criará um token codificado HMACSHA256 exclusivo, também conhecido como token da web JSON (JWT). O cliente armazena o JWT e faz todas as solicitações subsequentes ao servidor com o token anexado. O servidor autentica o usuário comparando o JWT enviado com a solicitação àquele que ele armazenou no banco de dados. Aqui está um diagrama simples do processo:

autenticação baseada em tokens

O que um token JWT contém?

O token é separado em três valores separados por pontos codificados em base 64. Cada valor representa um tipo diferente de dados:

Cabeçalho

Consiste no tipo de token (JWT) e no tipo de algoritmo de criptografia (HS256) codificado na base 64.

Carga útil

A carga útil contém informações sobre o usuário e sua função. Por exemplo, a carga útil do token pode conter o e-mail e a senha.

Assinatura

A assinatura é uma chave única que identifica o serviço que cria o cabeçalho. Nesse caso, a assinatura do token será uma versão codificada em base 64 da chave secreta do aplicativo Rails ( Rails.application.secrets.secret_key_base). Como cada aplicativo tem uma chave base exclusiva, essa chave secreta serve como assinatura de token.

Configurando uma autenticação baseada em token com Rails 5

Chega de teoria, é hora de praticar. A primeira etapa é criar um novo aplicativo Rails 5 apenas API:

1rails _5.0.0.beta3_ new api-app --api
bash

Ao anexar --apino final do gerador, um aplicativo somente API será criado. Aplicativos somente API são adições recentes à plataforma Rails. Um aplicativo API é uma versão reduzida do aplicativo Rails padrão sem nenhum middleware desnecessário, como .erbvisualizações, ajudantes e ativos. Os aplicativos API vêm com middlewares especiais, como ActionController::APIaceleração de solicitação, configuração fácil de CORS e outros recursos personalizados dispensados ​​para a construção de APIs.

Existem vários requisitos que precisam ser atendidos antes de podermos usar a abordagem baseada em tokens:

  • Precisamos de um modelo acessível.
  • Uma maneira de codificar e decodificar tokens JWT deve ser implementada.
  • Precisamos de métodos para verificar se o usuário está autenticado.
  • Controladores para criar e fazer login de usuários também são necessários.
  • Precisamos de rotas para a criação de usuários e seu login e logout.

Criação do modelo de usuário

Primeiro, o modelo de usuário deve ser criado:

1 rails g model User name email password_digest
bash

Execute as migrações:

1 rails db:migrate
bash

Ao executar esses métodos, criamos um modelo de usuário com os campos de nome, e-mail e senha e temos seu esquema migrado no banco de dados.

O método has_secure_passworddeve ser adicionado ao modelo para garantir que a senha esteja devidamente criptografada no banco de dados: has_secure_passwordé parte do bcryptgem, portanto, temos que instalá-lo primeiro. Adicione ao gemfile:

1#Gemfile.rb
2gem 'bcrypt', '~> 3.1.7'
rubi

E instale-o:

1 bundle install
bash

Com a gema instalada, o método pode ser incluído no modelo:

1#app/models/user.rb
2
3class User < ApplicationRecord
4 has_secure_password
5end
rubi

Tokens JWT de codificação e decodificação

Assim que o modelo do usuário for concluído, a implementação da geração do token JWT pode começar. Primeiro, a jwtgem tornará a codificação e decodificação de tokens HMACSHA256 disponíveis no aplicativo Rails. Primeiro:

1 gem 'jwt'
rubi

Em seguida, instale-o:

1bundle install
bash

Uma vez que o gem é instalado, ele pode ser acessado através da JWTvariável global. Como os métodos que serão usados ​​exigem encapsulamento, uma classe singleton é uma ótima maneira de envolver a lógica e usá-la em outras construções.

Para aqueles que não estão familiarizados, uma classe singleton restringe a instanciação de uma classe a um único objeto, o que é útil quando apenas um objeto é necessário para completar as tarefas em mãos.

1# lib/json_web_token.rb
2
3class JsonWebToken
4 class << self
5   def encode(payload, exp = 24.hours.from_now)
6     payload[:exp] = exp.to_i
7     JWT.encode(payload, Rails.application.secrets.secret_key_base)
8   end
9
10   def decode(token)
11     body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
12     HashWithIndifferentAccess.new body
13   rescue
14     nil
15   end
16 end
17end
rubi

O primeiro método encode,, usa três parâmetros - o ID do usuário, o tempo de expiração (1 dia) e a chave de base única de seu aplicativo Rails - para criar um token único.

O segundo método decode,, pega o token e usa a chave secreta do aplicativo para decodificá-lo.

Aqui estão os dois casos em que esses métodos serão usados:

  • Para autenticar o usuário e gerar um token para ele utilizar encode.
  • Para verificar se o token do usuário anexado em cada solicitação está correto, use decode.

Para ter certeza de que tudo funcionará, o conteúdo do libdiretório deve ser incluído quando o aplicativo Rails for carregado.

1    #config/application.rb
2module ApiApp
3  class Application < Rails::Application
4    #.....
5    config.autoload_paths << Rails.root.join('lib')
6    #.....
7    end
8   end
rubi

Autenticação de usuários

Em vez de usar métodos de controlador privado, simple_commandpode ser usado. Para obter mais informações sobre a instalação, consulte o artigo simple_command .

O comando simples gem é uma maneira fácil de criar serviços. Sua função é semelhante à de um auxiliar, mas facilita a conexão entre o controlador e o modelo, em vez do controlador e a visualização. Desta forma, podemos encurtar o código nos modelos e controladores.

Adicione a joia ao seu Gemfile:

1gem 'simple_command'
rubi

E agrupe-o:

1bundle install
bash

Então, os métodos de apelido do simple_commandpodem ser facilmente usados ​​em uma classe por escrito prepend SimpleCommandAqui está como um comando é estruturado:

1class AuthenticateUser
2   prepend SimpleCommand
3
4  def initialize()
5   #this is where parameters are taken when the command is called
6  end
7
8  def call
9   #this is where the result gets returned
10  end
11
12end
rubi

O comando obtém o e-mail e a senha do usuário e retorna o usuário, se as credenciais corresponderem. Veja como isso pode ser feito:

1# app/commands/authenticate_user.rb
2
3class AuthenticateUser
4  prepend SimpleCommand
5
6  def initialize(email, password)
7    @email = email
8    @password = password
9  end
10
11  def call
12    JsonWebToken.encode(user_id: user.id) if user
13  end
14
15  private
16
17  attr_accessor :email, :password
18
19  def user
20    user = User.find_by_email(email)
21    return user if user && user.authenticate(password)
22
23    errors.add :user_authentication, 'invalid credentials'
24    nil
25  end
26end
rubi

O comando leva os parâmetros e inicializa uma instância de classe com emailpasswordatributos que são acessíveis dentro da classe. O método privado userusa as credenciais para verificar se o usuário existe no banco de dados usando User.find_by_email.

Se o usuário for encontrado, o método usa o método integrado authenticateEste método pode estar disponível colocando has_secure_password no modelo de usuário para verificar se a senha do usuário está correta. Se tudo for verdade, o usuário será devolvido. Caso contrário, o método retornará nil.

Verificando a autorização do usuário

A criação do token está concluída, mas não há como verificar se um token que foi anexado a uma solicitação é válido. O comando para autorização deve obter o headersda solicitação e decodificar o token usando o decodemétodo no JsonWebTokensingleton.

Uma atualização sobre os cabeçalhos:

As solicitações Http têm campos conhecidos como cabeçalhos . Os cabeçalhos podem conter uma ampla variedade de informações sobre a solicitação que podem ser úteis para o servidor que a interpreta. Por exemplo, um cabeçalho pode conter o formato do corpo da solicitação, informações de autorização e outras meta informações (você pode encontrar todos os tipos aqui ). Os tokens são geralmente anexados ao cabeçalho 'Autorização'.

Aqui está como o código está estruturado:

1# app/commands/authorize_api_request.rb
2
3class AuthorizeApiRequest
4  prepend SimpleCommand
5
6  def initialize(headers = {})
7    @headers = headers
8  end
9
10  def call
11    user
12  end
13
14  private
15
16  attr_reader :headers
17
18  def user
19    @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
20    @user || errors.add(:token, 'Invalid token') && nil
21  end
22
23  def decoded_auth_token
24    @decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
25  end
26
27  def http_auth_header
28    if headers['Authorization'].present?
29      return headers['Authorization'].split(' ').last
30    else
31      errors.add(:token, 'Missing token')
32    end
33    nil
34  end
35end
rubi

Este código executa uma cadeia de métodos. Vamos começar de baixo e continuar até o topo.

http_auth_header

O último método na cadeia http_auth_header,, extrai o token do cabeçalho de autorização recebido na inicialização da classe.

decoded_auth-token

O método anterior na cadeia é decoded_auth_token, que decodifica o token recebido http_auth_headere recupera o ID do usuário.

do utilizador

A lógica do usermétodo pode parecer abstrata, então vamos examiná-la linha por linha.

Na primeira linha, o ||=operador é usado para atribuir @useratribuindo "se não nil". Basicamente, se o User.find()retorna um conjunto vazio ou decoded_auth_tokenretorna falso, @userserá nil.

Passando para a segunda linha, o usermétodo retornará o usuário ou gerará um erro. Em Ruby, a última linha da função é retornada implicitamente, então o comando acaba retornando o objeto do usuário.

Implementando métodos auxiliares nos controladores

Toda a lógica para lidar com tokens JWT foi estabelecida. É hora de implementá-lo nos controladores e colocá-lo em uso real. As duas partes mais essenciais a serem implementadas são a identificação do login do usuário e a referência ao usuário atual.

Usuários de login

Primeiro, vamos começar com o login do usuário:

1# app/controllers/authentication_controller.rb
2
3class AuthenticationController < ApplicationController
4 skip_before_action :authenticate_request
5
6 def authenticate
7   command = AuthenticateUser.call(params[:email], params[:password])
8
9   if command.success?
10     render json: { auth_token: command.result }
11   else
12     render json: { error: command.errors }, status: :unauthorized
13   end
14 end
15end
rubi

authenticateação pegará os parâmetros JSON para e-mail e senha por meio do paramshash e os passará para o AuthenticateUsercomando. Se o comando for bem-sucedido, ele enviará o token JWT de volta ao usuário.

Vamos colocar um ponto final para a ação:

1  #config/routes.rb
2  post 'authenticate', to: 'authentication#authenticate'
rubi

Solicitações de autorização

Para colocar o token em uso, deve haver um current_usermétodo que irá 'persistir' o usuário. Para estar à current_userdisposição de todos os controladores, deve ser declarado em ApplicationController:

1#app/controllers/application_controller.rb
2class ApplicationController < ActionController::API
3 before_action :authenticate_request
4  attr_reader :current_user
5
6  private
7
8  def authenticate_request
9    @current_user = AuthorizeApiRequest.call(request.headers).result
10    render json: { error: 'Not Authorized' }, status: 401 unless @current_user
11  end
12end
rubi

Ao usar before_action, o servidor passa os cabeçalhos da solicitação (usando a propriedade do objeto embutido request.headers) para AuthorizeApiRequestcada vez que o usuário faz uma solicitação. Chamando resulton AuthorizeApiRequest.call(request.headers)é proveniente do SimpleCommandmódulo, onde é definido como attr_reader :resultOs resultados da solicitação são devolvidos ao @current_user, tornando-se disponíveis para todos os controladores herdados de ApplicationController.

Funciona?

Vamos ver como tudo funciona. Inicie o console Rails no diretório raiz do aplicativo:

1rails c
bash

Crie um usuário e insira-o no console:

1User.create!(email: 'example@mail.com' , password: '123123123' , password_confirmation: '123123123')
bash

Para ver como a autorização funciona, deve haver um recurso que deve ser solicitado. Vamos criar um recurso. Em seu terminal, execute:

1rails g scaffold Item name:string description:text
bash

Isso criará um recurso nomeado Itemde cima para baixo - um modelo, um controlador, rotas e visualizações. Migre o banco de dados:

1rails db:migrate
bash

Agora, inicie o servidor e use cURL para postar as credenciais localhost:3000/authenticateEsta é a aparência da solicitação:

1$ curl -H "Content-Type: application/json" -X POST -d '{"email":"example@mail.com","password":"123123123"}' http://localhost:3000/authenticate
bash

Seu token será devolvido.

1{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA"}
bash

Excelente! Um token foi gerado. Vamos verificar se o recurso está acessível. Você pode fazer isso fazendo uma GETsolicitação para localhost:3000/items:

1$ curl http://localhost:3000/items
2{"error":"Not Authorized"}
bash

O recurso não está acessível porque o token não foi anexado aos cabeçalhos da solicitação. Copie o token gerado anteriormente e coloque-o no Authorizationcabeçalho:

1$ curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA" http://localhost:3000/items
2[]
bash

Com o token prefixado, um array vazio ( []) é retornado. Isso é normal - depois de adicionar quaisquer itens, você os verá retornados na solicitação.

Incrível! Tudo funciona.

Se você perdeu algo, o projeto foi carregado no GitHub . Se você tiver alguma dúvida, sinta-se à vontade para me enviar uma mensagem no Github.

Comentários

Postagens mais visitadas deste blog