DEV Community

Code Salley
Code Salley

Posted on • Updated on

Ruby on Rails api authentication with devise and Doorkeeper

Ruby on Rails web authentication with devise is easy to get started with, but how can we handle authentication with devise and doorkeeper in a rails api. This article focuses on steps to add authentication after creating a new project.

Buckle up, required gems installation, Starting with devise.

Setup devise
# add to Gemfile
gem 'devise'

# install gem
bundle install 

# devise initialization.
# for more context on devise refer to official 
rails generate devise:install

# generate User model migration. 
rails generate devise User 

# Run rails migration 
rails db:migrate

Enter fullscreen mode Exit fullscreen mode

Devise docs

Setup Doorkeeper
# add doorkeeper to Gemfile 
gem 'doorkeeper'

# install gem 
bundle install 

# initialize doorkeeper 
rails generate doorkeeper:install 

# Above command integrate doorkeeper into your 
# project and create and initializer file in 
# config/initializers/doorkeeper.rb, add doorkeeper 
# routes to your route file(config/routes.rb) and locale 
# file in config/locales/doorkeeper.en.yml 
# for more insight check official docs. 

# generate doorkeeper migration 
rails generate doorkeeper:migration

# run rails migration 
rails db:migrate

Enter fullscreen mode Exit fullscreen mode

Doorkeeper Official docs

We need two(2) routes for login and signup. In config/routes.rb lets comment out use_doorkeeper and devise routes. Create new routing to look something like this.

# config/routes.rb

# comment or remove doorkeeper routes
# use_doorkeeper

# not exposing devise routes 
devise_for :users, only: [] 

# our auth routes auth/signup and auth/login
scope "auth" do 
  post "/signup",  to: "auth#signup"
  post "/login",  to: "auth#login"

Enter fullscreen mode Exit fullscreen mode

doorkeeper configuration needs some adjustment since we're using custom routes.

# config/initializers/doorkeeper.rb

Doorkeeper.configure do
  # Change the ORM that doorkeeper will use (requires ORM extensions installed).
  # Check the list of supported ORMs here:
  orm :active_record

  # This block will be called to check whether the resource owner is authenticated or not.
  resource_owner_authenticator do
    current_user || warden.authenticate!(scope: :user)

  resource_owner_from_credentials do |_routes|
    User.authenticate!(params[:email], params[:password]) # we need to add this method in our user model

  grant_flows %w[authorization_code client_credentials password]


  client_credentials :from_basic, :from_params

  access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param

  access_token_expires_in 1.hour
Enter fullscreen mode Exit fullscreen mode

lets adjust User model to include authenticate! method

# app/models/user.rb
class User < ApplicationRecord

# add this method to find and authenticate users
  def self.authenticate!(email, password)
    user = find_by(email: email.downcase)
    user if user&.valid_password?(password)
Enter fullscreen mode Exit fullscreen mode

One of the very capabilities of doorkeeper is the ability to manage multiple platforms, like a doorkeeper client app for android, web etc. In our database seed (db/seeds.rb) lets create an app for our IOS app like this.

# db/seeds.rb

Doorkeeper::Application.find_or_create_by(name: "IOS APP") do |app|
  app.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
  app.secret = "my_secret" 
  app.uid = "my_uid"!

Enter fullscreen mode Exit fullscreen mode

run rails db:seed to insert above doorkeeper app into our database.

Now we need auth controller to be able to handle requests from our signup and login routes. create a file app/controllers/auth_controller.rb

# app/controllers/auth_controller.rb

# signup method
def signup
    client_app = Doorkeeper::Application.find_by(uid: params[:client_id])

    unless client_app
      return render json: { error: I18n.t("doorkeeper.errors.messages.invalid_client") },
          status: :unauthorized

    @user =

    unless @user
      render json: { message: "registration failed" }, status: :unprocessable_entity

    @results = model_results(@user, client_app)


def login
    response = strategy.authorize
    @token = response.status == :ok ? response.token : nil
    if @token&.resource_owner_id
      @user ||= User.find(@token.resource_owner_id)

    self.response_body =
      if response.status == :unauthorized
        render json: {error: "unauthorized" }, status: 404
        user_json(@user, @token)


# for a more cleaner approach, separate this into concerns or an isolated class.

def model_results(user, client_app, token_type = "Bearer")
   access_token = Doorkeeper::AccessToken.find_or_create_for(
        application:    client_app,
        refresh_token:  generate_refresh_token,
        expires_in:     Doorkeeper.configuration.access_token_expires_in.to_i,
        scopes:         ""

  return { user: user, tokens: {refresh_token: access_token.refresh_token, access_token:  access_token.token }

 def generate_refresh_token
    loop do
      token = SecureRandom.hex(32)

      break token unless Doorkeeper::AccessToken.exists?(refresh_token: token)

def user_json(user, token)
      user:          user,
      auth: {

        access_token:  token.token,
        refresh_token: token.refresh_token

  def user_params
    params.require(:user).permit(:email ,:password, :password_confirmation)

Enter fullscreen mode Exit fullscreen mode

Finally how do we secure endpoints?
For any controller actions we desire to protect, we place this in our controller.

class ProfileController < ApplicationController
  # this protects profile actions. 
  before_action :doorkeeper_authorize!

  def create 
Enter fullscreen mode Exit fullscreen mode

Happy Coding 🎉

Top comments (4)

tnypxl profile image

If you're a reader stumbling upon this, there will be many changes and fixes required to make this implementation function properly.

codesalley profile image
Code Salley

Can you be more descriptive. Thanks

tnypxl profile image
tnypxl • Edited
  • This one isn't your fault, but there is a bug in the gem that makes it look for the doorkeeper initializer before it's been created when running rails g doorkeeper:install. It’s been brought up in doorkeeper’s GitHub as an issue. The work-around is creating the initializer manually with an empty Doorkeeper.configure do; end block and then running rails g doorkeeper:install. You might make a note in the post about it as no fix has been committed.
  • The user_json method is used as though it accepts arguments. But the method is not written to accept any arguments.
  • The user_params method should require :auth and not :users.
  • In the signup method, you are not rendering the model_results as json, so nothing comes across in the response.

I don't see how any of your code could have worked as-is. Typically, for tutorials like this, its a good idea to publish a repository of the working code just in case there are dependency issues or caveats and considerations not covered in the tutorial.

walem2707 profile image
Walem • Edited

Where is the strategy method, im getting an error in that line strategy.authorize. undefined local variable or method `strategy' thanks