{"id":8512,"date":"2019-01-28T15:00:34","date_gmt":"2019-01-28T17:00:34","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=8512"},"modified":"2019-09-06T15:33:58","modified_gmt":"2019-09-06T18:33:58","slug":"custom-authentication-methods-with-devise","status":"publish","type":"post","link":"http:\/\/blog.plataformatec.com.br\/2019\/01\/custom-authentication-methods-with-devise\/","title":{"rendered":"Custom authentication methods with Devise"},"content":{"rendered":"
In the past, we have been asked to include other authentication methods in Devise (e.g. token-based and magic email links<\/a>). Although it might make sense to include those for some applications, there is no plan to support them in Devise.<\/p>\n But don’t be upset, it turns out you might not need to override Devise’s This article will focus on how to include custom Warden strategies in a Rails application that uses Devise. If you want to know more about Warden strategies, I gave a talk<\/a> last year at RailsConf that explains them in more details.<\/p>\n The first part of this article will show how to set up a Rails application using Devise. If you want to skip to the token authentication part, click here<\/a>.<\/p>\n Create a new Rails application (this example uses Postgres as the database to take advantage of UUID<\/a>s to generate the access tokens):<\/p>\n Add the Now run the Devise generators:<\/p>\n We are going to need a column to store the First, create a migration to enable the Now edit the migration to call the The database is now able to use Now edit the migration like the one below. Notice the default is set to Don’t forget to create the database and run the migrations:<\/p>\n The next step is to create a user using It’s time to create the Warden strategy now!<\/p>\n Create a file In short, the strategy tries to find a user for the token sent in the Warden needs to know about this strategy. Create a file This allows Warden to recognise that it should call the Now it’s time to use the strategy. Create an Don’t forget to add a route for this controller action. Open To require authentication in the controller, the method You can see that this works using a simple It is also possible to define This will add the Now it’s possible to use Devise’s And… we’re done! The focus here was to show how to include custom Warden strategies in a Rails application. The example was straightforward but you can follow this structure to create custom authentication logic to suit your application’s needs.<\/p>\n The entire application used in this article can be found in GitHub<\/a>.<\/p>\n\n\n <\/p>\n","protected":false},"excerpt":{"rendered":" In the past, we have been asked to include other authentication methods in Devise (e.g. token-based and magic email links). Although it might make sense to include those for some applications, there is no plan to support them in Devise. But don’t be upset, it turns out you might not need to override Devise’s SessionsController … \u00bb<\/a><\/p>\n","protected":false},"author":54,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[36,7],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/8512"}],"collection":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/54"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=8512"}],"version-history":[{"count":18,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/8512\/revisions"}],"predecessor-version":[{"id":9298,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/8512\/revisions\/9298"}],"wp:attachment":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=8512"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=8512"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=8512"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}SessionsController<\/code> or monkey patch some of its internals. In this article, you’ll learn how to create a token-based authentication for a JSON API by relying on Warden’s features<\/a>.<\/p>\n
Disclaimers<\/h2>\n
Warden? Huh?<\/h3>\n
Show me the code!<\/h3>\n
Setup<\/h2>\n
rails new devise-token-based-auth --database=postgresql\n<\/code><\/pre>\n
devise<\/code> gem to your Gemfile:<\/p>\n
gem 'devise'\n<\/code><\/pre>\n
rails generate devise:install\nrails generate devise User\n<\/code><\/pre>\n
api_token<\/code>. For this, we’ll use the pgcrypto<\/a> extension’s
gen_random_uuid()<\/code> function.<\/p>\n
pgcrypto<\/code> extension:<\/p>\n
rails generate migration enable_pgcrypto_extension\n<\/code><\/pre>\n
#enable_extension<\/code> method:<\/p>\n
class EnablePgcryptoExtension < ActiveRecord::Migration[5.2]\n def change\n enable_extension 'pgcrypto'\n end\nend\n<\/code><\/pre>\n
pgcrypto<\/code>‘s functions. Now we can create a migration to add the
api_token<\/code> column:<\/p>\n
rails generate migration add_api_token_to_users\n<\/code><\/pre>\n
gen_random_uuid()<\/code>:<\/p>\n
class AddApiTokenToUsers < ActiveRecord::Migration[5.2]\n def change\n add_column :users, :api_token, :string, default: -> { 'gen_random_uuid()' }\n add_index :users, :api_token, unique: true\n end\nend\n<\/code><\/pre>\n
rails db:create db:migrate<\/pre>\n
rails console<\/code> and grab its
api_token<\/code>:<\/p>\n
rails console\nRunning via Spring preloader in process 60784\nLoading development environment (Rails 5.2.2)\nirb(main):001:0> user = User.create!(email: 'bruce@wayne.com', password: '123123')\n=> #\nirb(main):002:0> user.reload.api_token\n=> \"a4839b85-4c96-4f22-96f1-c2568e5d6a7f\"\n<\/code><\/pre>\n
The Api Token Strategy<\/h2>\n
app\/strategies\/api_token_strategy.rb<\/code> with the following content:<\/p>\n
class ApiTokenStrategy < Warden::Strategies::Base\n def valid?\n api_token.present?\n end\n\n def authenticate!\n user = User.find_by(api_token: api_token)\n\n if user\n success!(user)\n else\n fail!('Invalid email or password')\n end\n end\n\n private\n\n def api_token\n env['HTTP_AUTHORIZATION'].to_s.remove('Bearer ')\n end\nend\n<\/code><\/pre>\n
Authorization<\/code> header. If it does, it signs the user in. Otherwise, it returns an error. If you are not familiar with the
success!<\/code> and
fail!<\/code> methods, watch the talk on the start of the blog post to get a sense on how Warden works.<\/p>\n
config\/initializers\/warden.rb<\/code> with the following code:<\/p>\n
Warden::Strategies.add(:api_token, ApiTokenStrategy)\n<\/code><\/pre>\n
ApiTokenStrategy<\/code> when it receives the
:api_token<\/code> symbol.<\/p>\n
Authenticating a user<\/h2>\n
UsersController<\/code> that renders the
current_user<\/code> in JSON:<\/p>\n
class UsersController < ApplicationController\n def show\n render json: current_user.to_json\n end\nend\n<\/code><\/pre>\n
config\/routes.rb<\/code> in your editor and include the following:<\/p>\n
Rails.application.routes.draw do\n devise_for :users\n resource :user, only: :show\nend\n<\/code><\/pre>\n
#authenticate!<\/code> should be called passing the desired strategy as a parameter:<\/p>\n
class UsersController < ApplicationController\n def show\n warden.authenticate!(:api_token)\n render json: current_user.to_json\n end\nend\n<\/code><\/pre>\n
curl<\/code> request:<\/p>\n
curl http:\/\/localhost:3000\/user -H 'Authorization: Bearer a4839b85-4c96-4f22-96f1-c2568e5d6a7f'\n\n{\"id\":3,\"email\":\"bruce@wayne.com\",\"created_at\":\"2018-12-26T13:45:37.473Z\",\"updated_at\":\"2018-12-26T13:45:37.473Z\",\"api_token\":\"a4839b85-4c96-4f22-96f1-c2568e5d6a7f\"}\n<\/code><\/pre>\n
:api_token<\/code> as a default strategy so that it’s called when no strategy is passed as a parameter. Add the following code in the
config\/initializers\/devise.rb<\/code> file:<\/p>\n
Devise.setup do |config|\n # The secret key used by Devise. Devise uses this key to generate...\n config.warden do |manager|\n manager.default_strategies(scope: :user).unshift :api_token\n end\n\n# ==> Mountable engine configurations...\nend\n<\/code><\/pre>\n
:api_token<\/code> strategy in the first position, followed by Devise’s default strategies (
:rememberable<\/code> and
:database_authenticatable<\/code>).<\/p>\n
#authenticate_user!<\/code> helper, and the
:api_token<\/code> will still be used:<\/p>\n
class UsersController < ApplicationController\n before_action :authenticate_user!\n\n def show\n render json: current_user.to_json\n end\nend\n<\/code><\/pre>\n
Summary<\/h2>\n