Search code examples
deviseruby-on-rails-5doorkeeper

Rails: Doorkeeper custom response from context


We are using Doorkeeper gem to authenticate our users through an API. Everything is working fine since we've implemented it few years ago, we are using the password grant flow as in the example:

resource_owner_from_credentials do |_routes|
  user = User.active.find_for_database_authentication(email: params[:username])

  if user&.valid_password?(params[:password])
    sign_in(user, force: true)
    user
  end
end

Doorkeeper is coupled with Devise, which enable reconfirmable strategy. As you can see in the code above, we are only allowing active users (a.k.a users with a confirmed email) to connect:

User.active.find_.....

Problem

Our specifications changed and now we want to return a different error on login (against /oauth/token) depending if the user has confirmed its email or not. Right now, if login fails, Doorkeeper is returning the following JSON:

{
  "error": "invalid_grant",
  "error_description": "The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
}

Ideally, we want to be able to return a custom description if and only if the current email trying to login is unconfirmed

We've checked the documentation on Doorkeeper but it does not seems to have an easy way (if any at all) to do this. The fact that resource_owner_from_credentials method is located in the config adds too much magic and not enough flexibility.

Any ideas ?


Solution

  • Ok so after digging a little bit, we found an easy way to work around this issue by overriding Doorkeeper::TokensController.

    # frozen_string_literal: true
    
    class TokensController < Doorkeeper::TokensController
      before_action :check_if_account_is_pending, only: :create
    
      private
    
      def check_if_account_is_pending
        user = User.find_by(email: params['username'])
        render json: unconfirmed_account_error if user && !user.confirmed?
      end
    
      def unconfirmed_account_error
        { error: 'invalid', error_description: 'You must validate your email address before login' }
      end
    end
    

    We also needed to make sure the routes were pointing to the custom controller:

    use_doorkeeper do
      controllers tokens: 'tokens'
    end
    

    Hope it can helps someone in the future