Ruby
Article

Authenticate All the Things with oPRO, the Basics

By Ilya Bodrov-Krukowski

Authentication in Rails

o2

This article was peer reviewed by Thom Parkin. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

For the past few months, I’ve written a bunch of topics covering various authentication solutions from low-level (AuthLogic) to full-fledged ones (Devise). Today we are going to discuss OAuth 2 once again, however this time I’ll show you how to build your own authentication provider and customize it as needed.

Today’s guest is oPRO (short for OAuth Provider) created by Richard Schneeman. This is a Rails engine that allows you to build your own authentication provider quickly and easily. oPRO comes with a bunch of pre-defined settings, controllers, views, and models. Most of these items can be extended or modified further.

If, for some reason, you have never worked with the OAuth 2 protocol, you can read this introductory tutorial or refer to my article on using OAuth 2 with Rails. To put it simply, this is a protocol that allows some third-party application to gain limited access to a service on a user’s behalf. The cool thing about it is the user can control what action(s) the app can perform without access to the user’s password.

oPRO makes only two assumptions about your setup:

  • You have some kind of a user model for authentication
  • You are using ActiveRecord

To see it in action and read some docs, check out this sample app.

This article will consist of three parts. We will cover a lot of different things:

  • Preparing authentication provider (let’s call it “server app”)
  • Preparing demo client application (“client app”)
  • Setting up basic authentication at the server app
  • Integrating oPRO
  • Providing basic OAuth workflow
  • Customizing the default views and controllers
  • Coding a simple API adapter
  • Adding custom data to the authentication hash
  • Working with scope
  • Adding functionality to refresh tokens
  • Rate limiting
  • Introducing a custom authentication solution for the server app
  • Exchanging user credentials for an access token

This seems like a lot of work to do, but I’ll be there to guide you on the way, so fear not!

The source code for the server and client applications can be found on GitHub.

Preparing Server Application

Take a deep breath and create a new Rails app. This is going to be our authentication provider:

$ rails new OproServer -T

For this demo I am using Rails 4.2, but oPRO is compatible with Rails 3.1 and 3.2, as well.

We will require two gems for now:

Gemfile

[...]
gem 'opro'
gem 'devise'
[...]

Devise is suggested as the default authentication solution by oPRO and, therefore, we will stick to it (however I will give you some instructions on using different authentication mechanisms).

Run

$ bundle install

Now let’s setup Devise. I am not going to provide a deep explanation, but if you want to learn more refer to this article.

Run the devise generators to provide some basic configuration and create a User model:

$ rails generate devise:install
$ rails generate devise User

Tweak the config/initializers/devise.rb file as necessary. Also, modify the layout to display flash messages as Devise relies on them:

views/layouts/application.html.erb

[...]
<% flash.each do |key, value| %>
  <div class="alert alert-<%= key %>">
    <%= value %>
  </div>
<% end %>
[...]

For this demo I’m not going to use any styling at all, because we have a lot of other things to do. Now run another generator to create an initializer file and mount routes for oPRO:

$ rails g opro:install

Lastly apply all the migrations:

$ rake db:migrate

Registering a New Client Application

By default, oPRO comes with a documentation page, so boot your server and navigate to localhost:3000/oauth_docs to access it. If it does not show up, make sure you haven’t skipped anything from the previous section.

When you are ready, create a static pages controller, a root route and a view:

pages_controller.rb

class PagesController < ApplicationController
end

config/routes.rb

[...]
root to: 'pages#index'
[...]

views/pages/index.html.erb

<h1>Welcome to my Auth provider!</h1>
<%= link_to 'Register a new client app', new_oauth_client_app_path %>

This link leads to a page where clients can register their applications, just like they do when receiving their key pair on Twitter or Facebook. You’ll be asked to authenticate when visiting this page, so register a sample user. On the “Create An OAuth Client Application” page enter your app’s name and click Create (the name can be changed later). You will be presented with client id and secret key pair, so leave this page open for now.

The next thing we need to do is create a client application, so proceed to the next step!

Preparing Client Application

Create yet another Rails app called OproClient:

$ rails new OproClient -T

We need a place to store our client ID and secret keys received on the previous step. Of course, we might place it right into the code, but that’s not very secure, so I’ll stick with environmental variables. In development mode values will be loaded from the config/local_env.yml file:

config/local_env.yml

opro_client_id: 'your_id'
opro_client_secret: 'your_secret'
opro_base_url: 'http://localhost:3000'

For convenience I’ve also included server’s base URL.

Now tweak the application.rb file to set ENV properly:

config/application.rb

[...]
if Rails.env.development?
  config.before_configuration do
    env_file = File.join(Rails.root, 'config', 'local_env.yml')
    YAML.load(File.open(env_file)).each do |key, value|
      ENV[key.to_s] = value
    end if File.exists?(env_file)
  end
end
[...]

Now you have access to ENV['opro_client_id'], ENV['opro_client_secret'], and ENV['opro_base_url'] from your code.

Don’t forget to exclude local_env.yml from version control:

.gitignore

[...]
config/local_env.yml

Add a static pages controller and a welcoming page:

pages_controller.rb

class PagesController < ApplicationController
end

config/routes.rb

[...]
root to: 'pages#index'
[...]

views/pages/index.html.erb

<h1>Welcome!</h1>
<%= link_to 'Authenticate via oPRO', "#{ENV['opro_base_url']}/oauth/new?client_id=#{ENV['opro_client_id']}&client_secret=#{ENV['opro_client_secret']}&redirect_uri=%2F%2Flocalhost%3A3001%2Foauth%2Fcallback" %>

There are a couple of things to note:

  • /oauth/new is the default oPRO’s route to authenticate users via OAuth.
  • You have to pass your client_id and client_secret for authentication to work correctly (later we will learn that this is not the only way).
  • You also have to specify a redirect_uri – this is where users will be redirected after authentication takes place.

So, as you can see, this is very similar to what other OAuth 2 providers do.

Not sure about you, but to me, this URL seems a bit too long so I’d like to move it somewhere else:

application_controller.rb

[...]
private

def new_opro_token_path
  "#{ENV['opro_base_url']}/oauth/new?client_id=#{ENV['opro_client_id']}&client_secret=#{ENV['opro_client_secret']}&redirect_uri=%2F%2Flocalhost%3A3001%2Foauth%2Fcallback"
end

helper_method :new_opro_token_path
[...]

I am placing it inside the controller, because later we’ll need to call it from other actions as well.

views/pages/index.html.erb

<h1>Welcome!</h1>
<%= link_to 'Authenticate via oPRO', new_opro_token_path %>

Now we need to setup a callback URL.

Callback URL

First of all, provide a new route for your client app:

config/routes.rb

[...]
get '/oauth/callback', to: 'sessions#create'
[...]

Now we need a SessionsController with a create action. When a user is redirected to this action, a code parameter will be sent by oPRO, so the URL will look like http://localhost:3001/?code=123 (we are using port 3001, because 3000 is already occupied by the server – don’t forget about it!). This code is then used to obtain the actual access token by sending an HTTP POST request to http://localhost:3000/oauth/token.json along with client ID and secret.

Therefore, we need a library to perform HTTP requests. There are multiple solutions available and of course you are free to choose your favorite, but I’m going to stick with rest-client because is simple and yet powerful.

Gemfile

[...]
gem 'rest-client'
[...]

Run

$ bundle install

and now create a new controller:

sessions_controller.rb

class SessionsController < ApplicationController
  def create
    response = JSON.parse RestClient.post("#{ENV['opro_base_url']}/oauth/token.json",
                    {
                        client_id: ENV['opro_client_id'],
                        client_secret: ENV['opro_client_secret'],
                        code: params[:code]
                    },
                    accept: :json)
    session[:access_token] = response['access_token']
    session[:refresh_token] = response['refresh_token']
    redirect_to root_path
  end
end

Using RestClient.post we send a POST request to http://localhost:3000/oauth/token.json and set the three required parameters. oPRO responds with JSON containing access and refresh tokens as well as an expires_in field saying how soon the token is going to become invalid (by default it does not expire at all, so we will put it aside for now).

Then, we use the generic json gem to parse the response and store tokens in the user’s session, redirecting them back to the main page.

Sample API Request

So far so good, but we need to test that the token is actually working by performing some request. Luckily, oPRO comes with a sample route http://localhost:3000/oauth_tests/show_me_the_money.json that responds with a success message only if the access token is valid.

Therefore, create a new controller:

api_tests_controller.rb

class ApiTestsController < ApplicationController
  def index
    redirect_to root_path and return unless session[:access_token]
    @response = JSON.parse RestClient.get("#{ENV['opro_base_url']}/oauth_tests/show_me_the_money.json",
                                          params: {
                                              access_token: session[:access_token]
                                          },
                                          accept: :json)
  end
end

If the access token is not set, simply redirect the user back to the main page. Otherwise, send a GET request to the sample route, provide the access token as the sole parameter and then parse the response.

In the corresponding view display the message returned by the server app:

views/api_tests/index.html.erb

<%= @response['message'] %>

Don’t forget to add the route:

config/routes.rb

[...]
resources :api_tests, only: [:index]
[...]

Lastly, modify the main page view:

views/pages/index.html.erb

<h1>Welcome!</h1>
<% if session[:access_token] %>
  <%= link_to 'Show some money', api_tests_path %>
<% else %>
  <%= link_to 'Authenticate via oPRO', new_opro_token_path %>
<% end %>

Now boot your server (don’t forget that port 3000 is occupied):

$ rails s -p 3001

and check how this all is working. When clicking the “Show some money” link you’ll see an “OAuth worked!” message – this means that we are on the right track.

If you are getting a 401 error message, that means that you are either not sending a token or it is invalid. Double check your code – you probably missed something while executing the previous steps.

Customizing Controllers and Views

You’ll likely want to customize the views or exclude some default controller provided by oPRO (for example, the controller with docs). This can be done by passing a hash of arguments to the mount_opro_oauth method inside your config/routes.rb file. This method supports the following options:

  • except – pass a symbol or an array of symbols to exclude some of the routes. Possible options:
    • docs – controller with guides
    • tests – controller with methods to test the API
    • client_apps – controller to manage client API applications
  • controllers – pass a hash with a controller’s name and a new path. Possible options:
    • oauth_docs – same as docs
    • oauth_tests – same as tests
    • oauth_client_apps – same as client_apps
    • oauth_new – controller to request permissions from the user. Note that you can only redefine the new action. create action will still be called from the default controller.

Full implementation of this method can be found here.

So first of all, let’s exclude the documentation routes. To do that, simply write:

config/routes.rb

[...]
mount_opro_oauth except: :docs
[...]

What I want to do is to tweak the page that users see when authenticating via our application. To do that, of course, we need a custom controller.

config/routes.rb

[...]
mount_opro_oauth controllers: {oauth_new: 'oauth/auth'}, except: :docs
[...]

We are namespacing AuthController under Oauth, so inside the controllers directory create an oauth folder with an auth_controller.rb file inside:

controllers/oauth/auth_controller.rb

class Oauth::AuthController < Opro::Oauth::AuthController
end

This custom controller inherits from Opro::Oauth::AuthController defined by oPRO. I am not monkey-patching any actions here, because we only need to change a route. Still, it is totally possible to override the new action as needed.

Create a view under views/oauth/new.html.erb and tweak it in any way you like. For example, you can add some explanations to the access rights that the app is requesting:

views/oauth/new.html.erb

[...]

<h2>Explanation</h2>

<ul>
  <li><strong>Read</strong> - the app will be able to read information about your account.
  This permission is required.</li>
  <li><strong>Write</strong> - the app will be able to modify your account.</li>
</ul>

Maybe include some branding and contact information here. You can use the same approach to customize other views as well.

Conclusion

So, in this part we laid foundation for the next steps. Currently, users can authenticate and perform requests to sample API actions. However, there is still much work to be done: we need to store hte user’s info inside the database, introduce more API methods, and refactor the code. Moreover, it will be great to fetch some other information about a user apart from tokens.

Therefore, in the next part of this article we will take care of all these issues. See you soon!

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.
  • http://www.omnipasteapp.com Calin

    What was wrong with Doorkeeper?

    • Ilya Bodrov

      Not sure what do you mean by that. I did not say that it is wrong in any way. My task is to present readers with multiple solutions that they may use. I am not dictating which one is the best. By the way, my series on doorkeeper is coming the next month. :)

      • http://www.omnipasteapp.com Calin

        My mistake, I initially thought you where the author of oPRO, in witch case I would have liked to know what where the shortcomings of the other framework.

        Very informative article. Looking forward to your the ones.

        • Ilya Bodrov

          Ah, that’s not me :) I only made some contributions, nothing more. Well, to put it straight doorkeeper seems to be more mature and ideed it is being much more actively maintained. AFAIK, Richard is not planning to further develop opro in the nearest future. Still, it was interesting to observe another solution to the same problem, discuss another point of view.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Ruby, once a week, for free.