
Uma característica comum de aplicações web profissionais é o uso de serviços externos para autenticação. Diversos motivos levam a isso, como por exemplo o amplo tool box ofertado pelas soluções disponíveis no mercado (verificação de email, Multi Factor Authentication, Passwordless Authentication, etc).
A integração desse serviço numa aplicação pode ser um desafio. Esse será o assunto abordado neste artigo, especificamente na parte de autenticação numa aplicação Rails API only.
Neste tutorial criaremos uma aplicação Rails API contendo um serviço de autenticação externa do Auth0 e utilizando como suporte a gem Knock.
Desenvolveremos de forma simples as funcionalidades de sign in e sign up utilizando os endpoints do Auth0. Também será detalhado como utilizar a gem Knock para realizar a autenticação do usuário com o que foi retornado pelo Auth0.
Porque utilizar o Auth0?
A escolha do Auth0 se deve por ser um serviço conhecido e bastante utilizado, pois possui um plano free muito atrativo (https://auth0.com/pricing) mesmo para aplicações que lidam com alguns milhares de usuários.
O Knock por sua vez foi implementado idealizando o suporte à integração com o Auth0, agilizando o trabalho do desenvolvedor. Existe pouca informação sobre como fazer isso, o que traz uma complexidade desnecessária para tarefa e justifica a criação deste artigo.
Dependências
- – Uma conta free no Auth0
- – Ruby (2.6.3)
- – Ruby on Rails (5.2.3)
Desenvolvimento
Configurando a aplicação no Auth0
- Auth0.
- Localize e entre na opção Applications
- Clique em Create application e selecione o tipo Machine to Machine Application. (O tipo de aplicação determina os parâmetros que poderemos configurar.)
- Depois selecione a api Auth0 Management API, clique em Select All e em Authorize
Utilizaremos a autenticação clássica através do password e esta opção pode estar desabilitada em sua aplicação.
- Certifique-se de ativar a autenticação por password acessando: página da sua aplicação > Settings > Advanced Settings > Grant Types
- Habilitando o checkbox Password e em seguida salvando a alteração realizada.
- No canto superior direito, clique no ícone do seu usuário e depois em Settings
- Em API Authorization Settings localize o campo Default Directory, preencha ele com Username-Password-Authentication e clique no botão salvar.
Gerando nosso projeto
1 – Crie uma aplicação Rails API-only rodando o seguinte comando:
1 |
rails _5.2.3_ new one-bit-auth |
2 – Crie o banco de dados executando no terminal:
1 |
rake db:create |
3 – Para representar o usuário logado, crie um model chamado User
1 |
rails g model User |
4 – A migration gerada deve ser completada com o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 |
class CreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :auth0_uid, null: false t.string :email, null: false t.timestamps end end end |
5 – Em seguida, execute crie a tabela users no banco de dados:
1 |
rake db:migrate |
6 – O arquivo user.rb
deve ser complementado com o seguinte:
1 2 3 |
class User < ApplicationRecord validates_presence_of :auth0_uid, :email end |
(O campo auth0_uid
nada mais é do que o ID do nosso usuário na conta do Auth0.)
Setando as variáveis de ambiente
1 – Para utilizar variáveis de ambiente adicione a gem dontenv-rails ao grupo de desenvolvimento:
1 2 3 4 5 6 7 8 9 10 11 12 |
... group :development do gem 'listen', '>= 3.0.5', '< 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' # Manage env variables. gem 'dotenv-rails' end ... |
2 – Instale rodando:
1 |
bundle install |
Obs: Por questões de boa prática, caso deseje, liste o arquivo .env
em seu .gitignore
3 – Crie um arquivo chamado .env
e adicione as seguintes variáveis:
1 2 3 4 5 |
AUTH0_AUDIENCE="https://[your-audience].auth0.com/api/v2/" AUTH0_CLIENT_SECRET="[your-client-secret]" AUTH0_RSA_DOMAIN="https://[your-domain]/.well-known/jwks.json" AUTH0_CLIENT_ID="[your-client-id]" AUTH0_DOMAIN="[your-domain]" |
Obs: Com exceção da AUTH0_RSA_DOMAIN, todas informações se encontram na página da aplicação criada no Auth0. Veja elas na aba Settings (AUTH0_CLIENT_SECRET, AUTH0_CLIENT_ID e AUTH0_CLIENT_ID) e APIs (AUTH0_AUDIENCE, descrito na página como API IDENTIFIER).
4 – Neste artigo você utilizará a API padrão da aplicação, nomeada como Auth0 Management API.
Sua AUTH0_RSA_DOMAIN estará no path padrão do Auth0 (/.well-known/jwks.json
)Exemplo: https://rwehresmann.auth0.com/.well-known/jwks.json
.
Configurando a gem Knock
1 – Adicione ao Gemfile
as gems Knock e Http:
1 2 3 4 5 6 7 8 |
... # Handle JWT with auth0 gem 'knock', '~> 2.0' # To perform http requests. gem 'http' ... |
A gem http
é apenas minha escolha pessoal para facilitar chamadas HTTP que serão necessárias dentro da nossa API.
2 – Dando continuidade, no terminal, instale as gems executando bundle install
e em seguida geraremos o arquivo de configuração do Knock com:
1 |
rails generate knock:install |
O arquivo de configuração knock.rb
lhe traz como comentários as configurações padrões da gem e brevemente aponta configurações alternativas. Sem mais delongas, tenha em mente que a gem não recebe atualizações há alguns anos e portanto alguns exemplos comentados estão defasados.
3 – Substitua o conteúdo desse arquivo pelo seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Knock.setup do |config| if Rails.env.test? config.token_secret_signature_key = -> { Rails.application.credentials.read } else config.token_audience = -> { ENV['AUTH0_AUDIENCE'] } # Auth0 uses RS256. config.token_signature_algorithm = 'RS256' # API secret from Auth0. config.token_secret_signature_key = -> { ENV['AUTH0_CLIENT_SECRET'] } # RS256 is an asymmetric algorithm, which means it requires a private and public key. jwks_raw = HTTP.get(ENV['AUTH0_RSA_DOMAIN']).body.to_s jwks_keys = Array(JSON.parse(jwks_raw)['keys']) config.token_public_key = OpenSSL::X509::Certificate.new(Base64.decode64(jwks_keys[0]['x5c'].first)).public_key end end |
Em maiores detalhes:
config.token_secret_signature_key
: obrigatório utilizarmos a chave de assinatura da nossa aplicação no Auth0 para todos ambientes com exceção do de teste . Em ambientes de teste não queremos realizar nenhuma chamada externa para a executar a autenticação. Portanto, mantive a opção padrão do Knock que é a leitura do que quer que esteja definido nas credentials do Rails OBS: não serão abordados testes automatizados com Knock neste exemplo, mas a ideia aqui é chamar a atenção de que para ambiente de teste a configuração é diferente.config.token_audience
: obrigatório utilizarmos o identificador da API da nossa aplicação no Auth0;config.token_signature_algorithm
: a configuração padrão nos traz o algoritmo HS256, mas a aplicação no Auth0 utiliza o algoritmo RS256;config.token_secret_signature_key
: obrigatório utilizarmos osecret token
da nossa aplicação no Auth0;config.token_public_key
: o algoritmo RS256 é um algoritmo assimétrico, o que significa que ele requer tanto de uma chave privada como uma pública. A chave pública fica no domínio que definimos em uma variável de ambiente, e o restante do código neste trecho simplesmente trata de extrair a chave pública de lá.
Criando nossos wrappers para as chamadas de sign up e sign in
Para este artigo, optei pela escolha de criar o signup.rb
e o signin.rb
dentro da seguinte estrutura de pastas: app/lib/auth0
. A implementação de ambos é simples, e seu funcionamento depende apenas de informar o email e senha do usuário em questão.
1 – Crie o arquivo signup.rb em app/lib/auth0 e coloque:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
module Auth0 class Signup def self.perform(email, password) HTTP.headers(accept: 'application/json') .post( "https://#{ENV['AUTH0_DOMAIN']}/dbconnections/signup", form: { client_id: ENV['AUTH0_CLIENT_ID'], email: email, password: password, connection: 'Username-Password-Authentication', } ) end end end |
2 – Crie o arquivo signin.rb em app/lib/auth0 e coloque:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
module Auth0 class Signin def self.perform(email, password) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE HTTP.headers(accept: 'application/json') .post( "https://#{ENV['AUTH0_DOMAIN']}/oauth/token", ssl_context: ctx, json: { grant_type: 'password', username: email, password: password, scope: 'openid', audience: ENV['AUTH0_AUDIENCE'], client_id: ENV['AUTH0_CLIENT_ID'], client_secret: ENV['AUTH0_CLIENT_SECRET'], } ) end end end |
Maiores detalhes sobre a gem http podem ser consultados na sua documentação. Os endpoints e formato de chamadas para o Auth0 estão disponíveis aqui. Agora testaremos essas classes no rails console
.
3- Inclua lib aos autoload_paths adicionando ao arquivo config/application.rb
1 |
config.autoload_paths += %W(#{config.root}/lib) |
4 – Para testar a autenticação abra o rails console:
1 |
rails c |
5 – Rode o seguinte comando para testar o SignUp:
1 2 3 4 |
response = Auth0::Signup.perform('onebitauth@mail.com', '@1234abcd') => #<HTTP::Response/1.1 200 OK {"Date"=>"Thu, 02 May 2019 01:08:56 GMT", "Content-Type"=>"application/json; charset=utf-8", "Content-Length"=>"87", "Connection"=>"close", "X-Auth0-Requestid"=>"7a13741ce42e6586c57a", "X-Ratelimit-Limit"=>"50", "X-Ratelimit-Remaining"=>"49", "X-Ratelimit-Reset"=>"1556759338", "Cache-Control"=>"private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0", "Strict-Transport-Security"=>"max-age=15724800", "X-Robots-Tag"=>"noindex, nofollow, nosnippet, noarchive"}> 2.6.3 :003 > response.to_s => "{\"_id\":\"5cca4328bf80c910f5e7be2c\",\"email_verified\":false,\"email\":\"onebitauth@mail.com\"}" |
Perfeito! O usuário foi criado e nós também podemos confirmar isso visualmente na conta do Auth0.
(Numa situação real, você provavelmente criaria uma classe que utilizaria esse wrapper de signup e adicionaria uma lógica para criar o usuário no seu banco de dados, salvando o ID retornado pelo Auth0. Neste artigo não trataremos dessa implementação, então crie o usuário manualmente.)
6 – Com o email e ID retornados crie um usuário no banco de dados rodando:
1 |
User.create!(auth0_uid: 'auth0|5cca4328bf80c910f5e7be2c', email: 'onebitauth@mail.com') |
OBS: Não esqueça de utilizar o email e o id retornado para você ao invés dos utilizados neste exemplo!
Note o formato do ID exemplificado acima. O que de fato representa o ID do usuário no Auth0 seguirá o formato provider|ID. Em caso de dúvidas, é possível consultar essa informação na página do usuário no Auth0, buscando por user_id na seção Identity Provider Attributes.
7 – Rode o seguinte comando para testar o SignIn:
1 2 3 4 |
response = Auth0::Signin.perform('onebitauth@mail.com', '@1234abcd') => #<HTTP::Response/1.1 200 OK {"Date"=>"Thu, 02 May 2019 01:13:20 GMT", "Content-Type"=>"application/json", "Content-Length"=>"2533", "Connection"=>"close", "X-Auth0-Requestid"=>"f83e1823d05513008635", "X-Ratelimit-Limit"=>"100", "X-Ratelimit-Remaining"=>"99", "X-Ratelimit-Reset"=>"1556760465", "Cache-Control"=>"private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0", "Pragma"=>"no-cache", "Strict-Transport-Security"=>"max-age=15724800", "X-Robots-Tag"=>"noindex, nofollow, nosnippet, noarchive"}> response.to_s => "{\"access_token\":\"Bn4f9oWN1eBw...\",\"id_token\":\"cHe3Fvsg\",\"scope\":\"openid profile email address phone read:current_user update:current_user_metadata delete:current_user_metadata create:current_user_metadata create:current_user_device_credentials delete:current_user_device_credentials update:current_user_identities\",\"expires_in\":86400,\"token_type\":\"Bearer\"}" |
Funcionou! Agora temos em mãos o meu token que será utilizada para autenticação na API Rails.
Alterando a implementação do Knock
Ao implementar qualquer tipo de autenticação numa aplicação Rails, acaba-se deparando com a necessidade de utilização de um método comum normalmente chamado de current_user
(que lhe retorna o usuário logado). O Knock possui este mesmo método, e é necessário alterar alguns detalhes em sua implementação interna para que a integração com o Auth0 funcione.
1 – Adicione o seguinte em seu knock.rb
:
1 2 3 4 5 6 7 8 9 |
... # Find our User by id in test environment, and auth0_uid in other environments. Knock::AuthToken.class_eval do def entity_for(entity_class) key_to_find = Rails.env.test? ? :id : :auth0_uid entity_class.find_by(key_to_find => @payload['sub']) end end |
Pontos chaves desta alteração:
- Na implementação descrita nesse artigo, para facilitar a compreensão nesse ponto, entenda que
entity_class.find_by
será o equivalente aUser.find_by(key_to_find => @payload['sub'])
; @payload
conterá informações extraídas do token (JWT), esub
é a entidade à quem o token pertence, normalmente representado por um ID (foge do escopo deste artigo explicar em maiores detalhes o JWT, mas para maiores informações você pode ler artigos como esse aqui);Knock::AuthToken.class_eval
nos permite reescrever a classeentity_for
do móduloKnock::AuthToken
, e é importante salientar que para que a alteração tenha efeito, isso precisa ser feito antes de qualquer comandoinclude
deste módulo (como neste exemplo, dentro doknock.rb
que é uminitializer
).
Com isso, agora a implementação do Knock está pronta para trabalhar com o Auth0. A implementação original do método reescrito pode ser consultada aqui.
Perceba que a alteração tem como propósito, basicamente, realizar um find_by(auth0_uid: @payload['sub'])
ao invés de usar um simples find
. Isso porque o nosso ID do model User
está sendo tratado como um número incremental, e não o ID retornado pelo Auth0, que neste caso estaremos salvando no campo auth0_uid
.
Autenticando na API Rails
Vamos ao passo final da integração.
1 – Adicione o módulo de autenticação do Knock ao application_controller.rb
:
1 2 3 |
class ApplicationController < ActionController::API include Knock::Authenticable end |
2 – Crie o controllerhome_controller.rb
com a action index
, adicionando o callback para a autenticação do usuário:
1 2 3 4 5 6 7 |
class HomeController < ApplicationController before_action :authenticate_user def index render json: current_user || 'The user isn\'t authenticated' end end |
3 – Adicione a rota para action implementada:
1 2 3 |
Rails.application.routes.draw do get :index, to: 'home#index' end |
Por fim, testaremos a autenticação na API.
Neste exemplo utilizaremos o comando curl
do Linux para realizar as chamadas a API Rails, mas você pode perfeitamente realizá-las utilizando algum software como o Postman.
4 – Num terminal, inicie o servidor com:
1 |
rails s |
5 – Em outro terminal, tente realizar uma chamada sem autenticação de usuário:
1 |
curl http://localhost:3000/index |
O retorno foi o status 401 (unauthorized).
6 – Agora, no rails console
, execute uma chamada para obter o token de autenticação do Auth0:
1 2 |
response = Auth0::Signin.perform('onebitauth@mail.com', '@1234abcd') response.to_s |
7 – Na sequência execute uma requisição igual ao comando abaixo.
Não se esqueça de sbustituar SEUTOKEN pelo access_token retornado anteriormente
1 |
curl -H 'Accept: application/json' -H "Authorization: Bearer SEUTOKEN" http://localhost:3000/index |
8 – Pronto! Foi retornado o usuário autenticado.
Dica bônus: investigando erros no Knock
A implementação da versão do Knock utilizada neste artigo não nos da muitas pistas sobre o motivo de receber o erro *401 (Unauthorized)* do Auth0. No entanto, podemos realizar uma pequena mudança online dating free dating site para evidenciar o erro.
1 – No knock.rb
, adicioe o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
... Knock::Authenticable.module_eval do def define_current_entity_getter(entity_class, getter_name) unless self.respond_to?(getter_name) memoization_var_name = "@_#{getter_name}" self.class.send(:define_method, getter_name) do unless instance_variable_defined?(memoization_var_name) current = Knock::AuthToken.new(token: token).entity_for(entity_class) instance_variable_set(memoization_var_name, current) end instance_variable_get(memoization_var_name) end end end end |
Não se preocupe em entender tudo o que está acontecendo neste método. A implementação original do método pode ser consultada https://onebitcode.com/dating-site-in-new-york-city/, e a alteração realizada apenas removeu uma estrutura de begin rescue
utilizada na chamada do entity_for
, forçando assim que vejamos o raise error do Knock::AuthToken
acontecer, quando acontecer.
Conclusão
Este artigo exemplificou como realizar, em alguns passos, a integração de uma API Rails com o serviço de autenticação Auth0, utilizando como suporte a gem Knock.
A falta de documentação da gem Knock exemplificando como utilizá-la em conjunto com o Auth0 pode desencorajar sua utilização para este propósito e até mesmo frustrar uma tentativa. Ter o conhecimento de que alguns pontos da implementação interna do Knock precisam ser alterados para realização da integração podem assustar ainda mais. No entanto, com as informações entregues acima todo o processo se torna relativamente simples e lhe entrega o Knock num estado funcional para ser utilizado com o Auth0.
Conclui-se então que a implementação realizada neste artigo pode ser um guia sobre como integrar um serviço externo para autenticação nos seus projetos.
O código-fonte desenvolvido está disponível no fort lauderdale single over 50 dating sites.

Não perca nenhum conteúdo
Receba nosso resumo semanal com os novos posts, cursos, talks e vagas o/
Primeira vez no OneBitCode? Curtiu esse conteúdo?
O OneBitCode tem muito mais para você!
O OneBitCode traz conteúdos de qualidade, e em português, sobre programação com foco em Ruby on Rails e também JavaScript.
Além disso, aqui sempre levamos à você conteúdos valiosos sobre a carreira de programação, dicas sobre currículos, portfólios, perfil profissional, soft skills, enfim, tudo o que você precisa saber para continuar evoluindo como Programador(a)!
Fique por dentro de todos os conteúdos o/
Nossas redes sociais:
📹 • https://youtube.com/Onebitcode [Live todas as terças-feiras às 19h)
💻 • https://linkedin.com/company/onebitcode
🙂 • https://facebook.com/onebitcode
📱 • https://instagram.com/one_bit_code
🐦 • https://twitter.com/onebitcode
Nossos cursos:
🥇 • Programador Full Stack Javascript em 8 Semanas
💎 • Curso Completo de Ruby
⚙ • Minicurso: API Rails 5 Completo
🐞 • Minicurso de Testes para Ruby on Rails com RSpec
Espero que curta nossos conteúdos e sempre que precisar de ajuda, fala com a gente!
Estamos aqui para você 🙂
Bem-vindo à família OneBitCode o/