Setting Up An OAuth Provider in Ruby on Rails With Doorkeeper and Devise

November 29, 2016

How To Setup an OAuth Provider in Ruby on Rails With Doorkeeper and Devise

OAuth logo

In a previous post on understanding OAuth I stated I had implemented an OAuth provider in Ruby on Rails 5 along with a test client. In this post, I describe how I implemented the provider.

The client will be described in an upcoming post.

Getting started with this tutorial

As a refresher, if you recall from the previous post on OAuth, the OAuth provider is responsible for giving your client application an access token so it can access the OAuth provider’s protected resource.

I present this tutorial as a series of steps. If any step is optional, I let you know in the subheading with a marking like (Optional).

Step 1 – Configure Doorkeeper In Your Existing Rails Application

In your Rails project Gemfile, add the following line and issue a bundle install command.

gem 'doorkeeper'

Once the gem is installed, it’s time run the gem’s install task as follows:

rails generate doorkeeper:install

This will install a configuration file in config/initializers/doorkeeper.rb.

Next, run a migration task to generate some database migrations for ActiveRecord. ActiveRecord is the default ORM doorkeeper is configured to run with.

rails generate doorkeeper:migration

Then issue a rake db:migrate command to make the changes to your database.

Step 2 – Routes

The following will be added automatically to your config/routes.rb file by doorkeeper’s installation tasks you ran in the previous step.

Rails.application.routes.draw do
  use_doorkeeper
  # your routes
end

Step 3 – Install Devise

I’m going to cover the bare minimum you need to get up and running with Devise but you can find more configuration options over at the main GitHub page.

Add the gem to your gemfile as follows:

gem 'devise'

Run the generator task:

rails generate devise:install

If you’d like, you can do the configuration that will appear in the console to setup more configuration options.

Step 4 – Install Devise User

# generate User model
rails g model User

rails g devise User

You should see something like the following in your terminal:

Running via Spring preloader in process 4931
      invoke  active_record
      create    db/migrate/20160920045132_add_devise_to_users.rb
      insert    app/models/user.rb
       route  devise_for :users

Next, I install some additional fields to help with this particular demonstration:

rails g migration AddFieldsToUsers first_name:string last_name:string company:string

And finally, I run the migrations:

bundle exec rake db:migrate

Step 3 – Authentication

In config/initializers/doorkeeper.rb I have:

Doorkeeper.configure do
  resource_owner_authenticator do
    # User.find_by_id(session[:current_user_id]) || redirect_to(login_url)
    if user_signed_in?
      current_user
    else
      redirect_to root_path
    end
  end
end

In this particular block of code, I’m using Devise’s built in helpers. I’m essentially returning the current_user if a user is signed in, otherwise, I redirect you to the root_path.

Step 4 – Protecting /oauth/applications

An easy way to do this is to use the HTTP basic authentication that Rails gives you out of the box.

class DoorkeeperClientAuthorizationController < ApplicationController
  http_basic_authenticate_with name: ENV['CLIENT_USER'], password: ENV['CLIENT_PASSWORD']

  def access_oauth_client_apps
    redirect_to oauth_applications_path
    session[:oauth_applications] = true
  end
end

In config/routes.rb:

# added for minimal security around adding trusted client apps for oauth
  get '/access_oauth_client_apps', to: 'doorkeeper_client_authorization_#access_oauth_client_apps'

In config/environments/development.rb

Rails.application.configure do
  ENV['CLIENT_USER'] = 'oauth_client'
  ENV['CLIENT_PASSWORD'] = 'password'
end

Step 5 (optional) - Send custom information along with the access token

For this, I setup a custom controller that inherits from Doorkeeper.

class CustomTokensController < Doorkeeper::ApplicationMetalController
  def create
    response = authorize_response
    headers.merge! response.headers
    body = response.body

    # modify the typical doorkeeper response
    if response.status == :ok
      # User the resource_owner_id from token to identify the user
      user = User.find(response.token.resource_owner_id) rescue nil

      unless user.nil?
        ### If you want to render user with template
        ### create an ActionController to render out the user
        # ac = ActionController::Base.new()
        # user_json = ac.render_to_string( template: 'api/users/me', locals: { user: user})
        # body[:user] = Oj.load(user_json)

        ### Or if you want to just append user using 'as_json'
        body[:email] = user.email
        body[:first_name] = user.first_name
        body[:last_name] = user.last_name
        body[:company] = "Company Example"
      end
    end

    self.response_body = body.to_json
    self.status        = response.status
  rescue Doorkeeper::Errors::DoorkeeperError => e
    handle_token_exception e
  end

  # OAuth 2.0 Token Revocation - http://tools.ietf.org/html/rfc7009
  def revoke
    # The authorization server, if applicable, first authenticates the client
    # and checks its ownership of the provided token.
    #
    # Doorkeeper does not use the token_type_hint logic described in the
    # RFC 7009 due to the refresh token implementation that is a field in
    # the access token model.
    if authorized?
      revoke_token
    end

    # The authorization server responds with HTTP status code 200 if the token
    # has been revoked successfully or if the client submitted an invalid
    # token
    render json: {}, status: 200
  end

  private

  # OAuth 2.0 Section 2.1 defines two client types, "public" & "confidential".
  # Public clients (as per RFC 7009) do not require authentication whereas
  # confidential clients must be authenticated for their token revocation.
  #
  # Once a confidential client is authenticated, it must be authorized to
  # revoke the provided access or refresh token. This ensures one client
  # cannot revoke another's tokens.
  #
  # Doorkeeper determines the client type implicitly via the presence of the
  # OAuth client associated with a given access or refresh token. Since public
  # clients authenticate the resource owner via "password" or "implicit" grant
  # types, they set the application_id as null (since the claim cannot be
  # verified).
  #
  # https://tools.ietf.org/html/rfc6749#section-2.1
  # https://tools.ietf.org/html/rfc7009
  def authorized?
    if token.present?
      # Client is confidential, therefore client authentication & authorization
      # is required
      if token.application_id?
        # We authorize client by checking token's application
        server.client && server.client.application == token.application
      else
        # Client is public, authentication unnecessary
        true
      end
    end
  end

  def revoke_token
    if token.accessible?
      token.revoke
    end
  end

  def token
    @token ||= Doorkeeper::AccessToken.by_token(request.POST['token']) ||
      Doorkeeper::AccessToken.by_refresh_token(request.POST['token'])
  end

  def strategy
    @strategy ||= server.token_request params[:grant_type]
  end

  def authorize_response
    @authorize_response ||= strategy.authorize
  end
end

In config/routes.rb:

use_doorkeeper do
  controllers :tokens => 'custom_tokens'
end

Step 6 (optional) - Skip authorization for trusted applications

In config/initializers/doorkeeper.rb

Doorkeeper.configure do
  skip_authorization do
    # allow all client apps to be "trusted"
    true
  end
end

Step 7 - Setup a protected resource endpoint

module Api
  module V1
    class UsersController &lt; ApplicationController
      before_action :doorkeeper_authorize!

      respond_to :json

      def me
        respond_with current_resource_owner
      end

      def user
        user = User.find_by(id: params[:id])
        respond_with user
      end

      private

      def current_resource_owner
        # find logged in user (via devise) if doorkeeper token
        User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
      end
    end
  end
end

In config/routes.rb:

Rails.application.routes.draw do

  namespace :api do
    namespace :v1 do
      resources :users, only: [:user] do
        collection do
          get :me
        end
      end
    end
  end

end

Step 8 - Setup a root path

Next, we’ll setup a default root path in our config/routes.rb file. If you don’t, you’ll see an error like the below in your console.

Rendered /Users/bruce/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/doorkeeper-4.2.0/app/views/doorkeeper/applications/index.html.erb within layouts/doorkeeper/admin (17.1ms)
Completed 500 Internal Server Error in 528ms (ActiveRecord: 3.5ms)

ActionView::Template::Error (undefined local variable or method `root_path' for #<#<Class:0x007fedc207df98>:0x007fedc206dd00>
Did you mean?  font_path):
    19:         <%= link_to t('doorkeeper.layouts.admin.nav.applications'), oauth_applications_path %>
    20:       <% end %>
    21:       <%= content_tag :li do %>
    22:         <%= link_to t('doorkeeper.layouts.admin.nav.home'), root_path %>
    23:       <% end %>
    24:     </ul>
    25:   </div>
    ```

In config/routes.rb, add:

```ruby
Rails.application.routes.draw do
  root to: 'static#index'
end

Next, create a StaticController:

rails g controller Static --no-assets --no-helper

Finally, create a view in app/views/static/index.html.haml

%h1 Welcome to OAuth Provider
  %p welcome placeholder

Summary

If you’ve made it this far, you have now setup an OAuth provider. In an upcoming post, I talk about how to implement a test client application so you can see everything in action.


Profile picture

Written by Bruce Park who lives and works in the USA building useful things. He is sometimes around on Twitter.