{"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 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

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

Show me the code!<\/h3>\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

Setup<\/h2>\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

rails new devise-token-based-auth --database=postgresql\n<\/code><\/pre>\n

Add the devise<\/code> gem to your Gemfile:<\/p>\n

gem 'devise'\n<\/code><\/pre>\n

Now run the Devise generators:<\/p>\n

rails generate devise:install\nrails generate devise User\n<\/code><\/pre>\n

We are going to need a column to store the api_token<\/code>. For this, we’ll use the pgcrypto<\/a> extension’s gen_random_uuid()<\/code> function.<\/p>\n

First, create a migration to enable the pgcrypto<\/code> extension:<\/p>\n

rails generate migration enable_pgcrypto_extension\n<\/code><\/pre>\n

Now edit the migration to call the #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

The database is now able to use 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

Now edit the migration like the one below. Notice the default is set to 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

Don’t forget to create the database and run the migrations:<\/p>\n

rails db:create db:migrate<\/pre>\n

The next step is to create a user using 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

It’s time to create the Warden strategy now!<\/p>\n

The Api Token Strategy<\/h2>\n

Create a file 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

In short, the strategy tries to find a user for the token sent in the 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

Warden needs to know about this strategy. Create a file config\/initializers\/warden.rb<\/code> with the following code:<\/p>\n

Warden::Strategies.add(:api_token, ApiTokenStrategy)\n<\/code><\/pre>\n

This allows Warden to recognise that it should call the ApiTokenStrategy<\/code> when it receives the :api_token<\/code> symbol.<\/p>\n

Authenticating a user<\/h2>\n

Now it’s time to use the strategy. Create an 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

Don’t forget to add a route for this controller action. Open 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

To require authentication in the controller, the method #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

You can see that this works using a simple 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

It is also possible to define :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

This will add the :api_token<\/code> strategy in the first position, followed by Devise’s default strategies (:rememberable<\/code> and :database_authenticatable<\/code>).<\/p>\n

Now it’s possible to use Devise’s #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

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}]}}