📚 Blog Archive

API authentication with devise_token_auth

· Miguel Parramón · blogger

This post is part of an ongoing feature about creating a social network in Rails.

Any modern Rails web app will need an API to power mobile and/or desktop clients, and maybe even a SPA later on. The devise_token_auth gem adds API user authentication to our app with minimum effort and total configurability. Here’s a quick guide on how to set it up, together with solutions to the gotchas that come with it.
We start on our Gemfile, as usual:

gem ‘devise’ gem ‘devise_token_auth’ # Token based authentication for Rails JSON APIs gem ‘omniauth’ # required for devise_token_auth

Now, let’s generate the User model that will use token authentication. “User” is our model’s name, while “auth” is the path where our authentication endpoints will be mounted.

rails g devise_token_auth:install User auth

This command will give us the following:

  • A user model with the proper devise modules for authentication, registration and so on, including the DeviseTokenAuth module for API authentication:

    class User < ActiveRecord::Base devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable include DeviseTokenAuth::Concerns::User end

  • A migration to setup the model:

    class DeviseTokenAuthCreateUsers < ActiveRecord::Migration def change create_table(:users) do |t| ## Required t.string :provider, :null => false t.string :uid, :null => false, :default => ""

      ## Database authenticatable
      t.string :encrypted\_password, :null => false, :default => ""
    
      # ...
      ## User Info
      t.string :name
      t.string :nickname
      t.string :image
      t.string :email
    
      ## Tokens
      t.text :tokens
    
      t.timestamps
    end
    
    add\_index :restaurant\_users, :email
    # ...

    end end

  • The routes for our auth endpoint (I namespaced them to add versioning for the API):

    namespace :api do scope :v1 do mount_devise_token_auth_for “User”, at: ‘auth’ end end

Finally, the reason why I wrote this blog post —the gotchas.

  • A user that uses DeviseTokenAuth can also be authenticated with normal Devise, but the Devise routes must come first. More info here.

  • Rails comes with CSRF prevention by raising an exception. This will make your token auth fail when accessed via the API. To fix it, change it on your app/controllers/application_controller.rb:

    class ApplicationController < ActionController::Base protect_from_forgery with: :null_session

    end

  • This gem comes with a sturdy security default configuration. Unless you’re using the ng-token-auth lib for authentication on Angular, I suggest to disable the change_headers_on_each_reques option on the gem’s config (config/initializers/devise_token_auth.rb), to ease development.

With this minimum investment, we get a battle-tested, full-fledged authentication solution. Here are the main endpoints our User auth API will have:

pathmethodpurpose
/POSTEmail registration. Accepts email, password, and password_confirmation params.
/DELETEAccount deletion. This route destroys users identified by their uid and auth_token headers.
/PUTAccount updates. This route updates an existing user’s account settings. The default accepted params are password and password_confirmation.
/sign_inPOSTEmail authentication. Accepts email and password as params. Return a JSON representation of the User model on successful login.
/sign_outDELETEUse this route to end the user’s current session, by invalidating their authentication token.
/validate_tokenGETUse this route to validate tokens on return visits to the client. Accepts uid and auth_token as params.
/passwordPOSTUse this route to send a password reset confirmation email to users that registered by email.
/passwordPUTUse this route to change users’ passwords.
/password/editGETVerify user by password reset token. This route is the destination URL for password reset confirmation.

Have you tried any other token-based solution for API authentication, or built one yourself? Having problems implementing this one on your app? Share it on the comments!

View original post →