Oauth2 on Rails

Oauth2 has become the defacto standard for online authentication: Twitter, Facebook, Four Square, Amazon are some of the big players that rely on it for their APIs.

The Oauth authentication process requires an authentication server application where users login, and a client application that uses the server application for user authentication.

We’re going to create both the client and server application from scratch with rails using devise, doorkeeper and omniauth-oauth2 gems to speed up the process, make them work together and analyze how the authentication works.

In this first post we’re going to build the server application step by step.

The server application

Devise will provide authentication, while doorkeeper will allow the app to work as an Oauth2 server. Let’s generate a new app:


rails new oauth-server

then add devise and doorkeeper gems to the Gemfile:


gem "devise"
gem "doorkeeper"

generate the gems boilerplate and migrate the db:


rails g devise:install
rails g devise User

rails g doorkeeper:install
rails generate doorkeeper:migration

rake db:migrate

A bunch of files are generated in the process, among them there’s the config/initializers/doorkeeper.rb initializer. Let’s open this file and modify it to allow doorkeeper to use devise for authentication. Change the settings on the top of it as follows, remembering to comment out the “fail” instruction:


resource_owner_authenticator do
  current_user || begin
    session[:user_return_to] = request.fullpath
    redirect_to new_user_session_url
  end
end

admin_authenticator do
  current_user || redirect_to(new_user_session_url)
end

The resource_owner_authenticator block allows devise authenticated users to access their resources via doorkeeper. If the user is not authenticated we store the url where he came from in the session, and we redirect him to the login page.
The admin_authenticator bock limits the access to the doorkeeper admin area only to registered users. This admin area is used to manage (create, update, destroy) the client application tokens required for the Oauth authentication.

You may want to limit the access to restricted group of user, for example to admins. This code may work for you:


admin_authenticator do
  current_user && current_user.admin? || redirect_to(new_user_session_url)
end

Then let’s create a dummy user that we’ll use for experimenting in db/seed.rb:


User.create! email: 'test@test.com', password: 'password', password_confirmation: 'password'

rake db:seed

Let’s create the actual action that will provide the client app with the authenticated user information. We could build a separate controller, but let’s use application controller for the sake of brevity:


class ApplicationController < ActionController::Base
  before_action :doorkeeper_authorize!, only: :me

  def me
    render json: User.find(doorkeeper_token.resource_owner_id).as_json
  end
end

and update the routes as well:


Rails.application.routes.draw do
  use_doorkeeper
  devise_for :users

  get '/me' => 'application#me'
end

The directive before_action :doorkeeper_authorize!, only: :me applies only to the me action, and it basically looks for a doorkeeper_token, an object built starting from the oauth authorization code, if not found it will render with a 401 Unauthorized header. The same token object is then used in the me action body to retrieve the user details, which will be rendered as JSON.

Let’s start the server and see what happens if we try to access the /me page:


rails s

curl -I localhost:3000/me
  HTTP/1.1 401 Unauthorized
  X-Frame-Options: SAMEORIGIN
  X-Xss-Protection: 1; mode=block
  X-Content-Type-Options: nosniff
  Cache-Control: no-store
  Pragma: no-cache
  WWW-Authenticate: Bearer realm="Doorkeeper", error="invalid_token", error_description="The access token is invalid"
  Content-Type: text/html
  X-Request-Id: 109af7db-5c09-4175-b6f3-cbd902f4caff
  X-Runtime: 0.003485
  Server: WEBrick/1.3.1 (Ruby/2.1.0/2013-12-25)
  Date: Fri, 30 Jan 2015 10:33:46 GMT
  Content-Length: 0
  Connection: Keep-Alive
  Set-Cookie: request_method=HEAD; path=/

As you can see, since we’re not authenticated and we didn’t provide a valid token the application responds with a 401 Unauthorized header.

Ideally the the client application will come to this url and get the user information, if authorized, and so the login process on the client app can complete.

So,everything is set for authentication, codewise, on the server application. The client application will need to be registered on the oauth-server app in order to be able to authenticate users. Let’s go and register it, heading your browser to http://localhost:3000/oauth/applications/new.
You will be redirected to the authentication page, fill in the form with the dummy user credentials in order to login (test@test.com, password) and you will be now presented a new form to create the new Oauth client application settings. Fill the form giving the name you prefer (I chose oauth-client) to the app and use this url as the redirect URI: http://localhost:3001/doorkeeper/callback.
This url must follow omniauth (used on the client app) conventions, where doorkeeper stands for the authentication strategy name (we’re going to build it later, the name could be anything meaningful for us) and callback is a path fixture for omniauth recognition. We will start the client app on localhost:3001, of course, and please consider switching to the more secure https protocol when you are production ready.
After pressing submit you will be presented with a success page showing the client app authentication settings, which include the application id, the secret key and the callback url. we’re going to use them later, so take a note.

image

Now let’s create the actual action that will provide the client app with the authenticated user information. We should build a separated controller, but let’s use application controller for brevity:


class ApplicationController < ActionController::Base
  before_action :doorkeeper_authorize!, only: :me

  def me
    render json: User.find(doorkeeper_token.resource_owner_id).as_json
  end
end

The line before_action :doorkeeper_authorize!, only: :me applies only to the me action, and it basically looks for a doorkeeper_token, an object built on the oauth authorization code, if not found it will render with a 401 Unauthorized header. The same token object is then used in the me action to retrieve the user information, which will be rendered in json format. Ideally the client application will come to this url and get the user information, if authorized, and so the login process on the client app can complete.
Let’s update the routes as well:


Rails.application.routes.draw do
  use_doorkeeper
  devise_for :users

  get '/me' => 'application#me'
end

The server application is now ready. Next time we will build the client application that will fetch the user data from the server. See you soon!

Leave a Reply

wpDiscuz