How to build an OAuth provider with User management in Ruby on Rails using Devise, Doorkeeper, ActiveAdmin and Cancancan

Photo by Micah Williams on Unsplash

I was recently given a task to build an Identity service on a short notice with Ruby on Rails. No fancy CSS in this prototype. This service will integrate authentication and authorization.

Project requirement:
* User can visit authorized apps after sign-up/sign-in from Identity service
* User can manage other users in the same company
* User can manage associated companies
* Admin can manage all Users, Companies and OAuth applications

This is a “How-to” tutorial. I assume you have a basic understanding of what OAuth2 is and the difference between authentication and authorization.

Gems used:
* Devise: Users authentication. (It’s for users to login and authenticate.)
* Doorkeeper: OAuth 2 provider. (In a nutshell, it’s for applications to authenticate.)
* ActiveAdmin: Provide administration interfaces for user managements.
* Cancancan: Users authorization. We will have 2 roles: User and Admin.
* Httparty: makes http requests

Later, I will build two client applications that auth against this Identity service to tie everything together.

The Goal:

  1. User visit a client app. (I will call it “Client-One”)
  2. Redirect user to the Identity sign-in page
  3. User can sign-up or sign-in
  4. OAuth takes place between Identity and the client app behind the scene
  5. When the dance is finished, redirect user back to the client app with an access token.
  6. User visit the second client app. (I will call it “Client-Two”)
  7. Since user has already logged in, it will redirect user to the second app directly.

Contents:
* Step 1 to Step 8: Identity Service
* Step 9 to Step15: Client applications
* Step 16 to Step19: Cancancan and ActiveAdmin
* Github repos and Demo apps

Let’s start with the Identity Service:

Step 1: Build a new Rails app. (I’m on Rails 5.2.4.2 at the time of writing.)

runrails new Identity --database=postgresql -T

cd into the project

Step 2: Devise

Add Device to Gemfile. gem 'devise' and run the following:

1. bundle install

2. rails g devise:install

3. rails g model User and rails g devise User

4. bundle exec rake db:create and bundle exec rake db:migrate

Step 3: Doorkeeper

Add Doorkeeper to Gemfile. gem 'doorkeeper' and run the following:

  1. bundle install
  2. rails g doorkeeper:install
  3. rails g doorkeeper:migration
  4. Open the newly generated migration file, _create_doorkeeper_tables.rb Uncomment the twoadd_foreign_key lines at the bottom. Replace <model> with :users.
  5. bundle exec rake db:migrate

Step 4: Setup Doorkeeper

In doorkeeper.rb , replace resource_owner_authenticator block with:

If user is logged in, return current_user (`current_user` is Devise method). If not, store the original request url in a session and send user to the sign-in page. After user signed in, the session[:previous_url] will be used to redirect user back to where they came from by the adding the following in application_controller.rb :

Step5: Home page

Let’s setup a home screen and controller.

In routes.rb add get '/', to: 'home#index', as: :home

Create a new file called /app/controllers/home_controller.rb and add the following:

Create a new file called /app/view/home/index.html.erb and add the following:

Now, fire up rails server and visit localhost:3000 . You should see the sign in page. Time to sign up an account for yourself!

You should see something like this after successful login.

Step 6: Add admin to user table

run rails g migration AddAdminToUser admin:boolean

open up the migration file and add ,default: false to the end of the add_column line

run rake db:migrate

Make yourself an admin! run

  1. bundle exec rails c
  2. User.last.update(admin:true)

Step 7: More Doorkeeper setup

Open doorkeeper.rb . Add the following after the resource_owner_authenticator block:

This allows admin to visit the Applications dashboard.

Fire up rails server, visit http://localhost:3000/oauth/applications, you should see a page where you can manage client applications. We will come back to this page later to add applications.

Step 8: Add a protect resource endpoint for the clients

Client apps can call this endpoint for user’s (resource owner) info.

In routes.rb , add the following:

Create a new file /app/controllers/api/users_controller.rb , add the following:

Let’s build the client app to talk to our Identity service.

Step 9: Build a new Rails app. (I’m on Rails 5.2.4.2 at the time of writing.)

run rails new client-one --database=postgresql -T

run bundle exec rake db:create and bundle exec rake db:migrate

Step 10: Add httparty

Open Gemfile , add gem 'httparty' and run bundle install

Step 11: Update routes

Open routes.rb and add:

Step 12: Create home controller

create /app/controller/home_controller.rb and add the following:

If user visit page with access token, call Identity and fetch resource owner’s info. If user visit page without a token, return user to Identity to sign-in.

create_session is the callback for the auth-code/token exchange.

Step 13: Create a home view

create /app/views/home/index.html.erb and add the following:

Step 14: Register this new client in Identity service

Fire up Identity and visit http://localhost:3000/oauth/applications

Now you can add a new application.
* Name: “Client One”
* Redirect URI: “http://localhost:3001/oauth/callback”
Click “Submit” and remember the UID and Secret.

Step 15: environment variables

create a .env file in this project root directory. It looks something like this:

goto your terminal and run source .env , it will load these variables.

NOTE: Do not commit your secrets to Github. Put .env in .gitignore

run bundle exec rails s -p 3001 , the client app will run on localhost:3001 and you should see “You are log in as your-email@example.com”

Repeat Step 9 to Step 15 to create Client app two. You pretty much just have to replace “one” with “two”, “two” with “one”. Run client app two on localhost:3002 . You should be able to visit Client-One and Client-Two.

Now, anyone who signed up for an account can access the Identity main page, which manages users, companies and applications. That’s not ideal! Let’s add Cancancan into Identity so that only admin can access the Applications page.

Step 16: Create acompanies table and reference to user

run rails g model Company name
run rails g migration CreateCompaniesUsers user:references company:references
run rake db:migrate

Step 17: Update models

in /app/models/user.rb add: has_and_belongs_to_many :companies
in /app/models/company.rb add: has_and_belongs_to_many :users

Step 18: Add Cancancan

open Gemfile , addgem 'cancancan' and run bundle install

run rails g cancan:ability

open /app/models/ability.rb replace the whole thing with:

Admin has full access to User, Company and manage the ActiveAdmin dashboard.
User can manage users belongs to his/her company, companies belongs to the user and read the ActiveAdmin dashboard.

Step 19: Add ActiveAdmin

open Gemfile ,gem 'activeadmin' and run bundle install

run rails g active_admin:install and rails db:migrate
run rails g active_admin:resource User
run rails g active_admin:resource Company

in routes.rb change

to

in /config/initializers/active_admin.rb , replace everything with:

Now you should be able to visit http://localhost:3000/admin/users

Let’s customize the view. We don’t need to see every columns.

In /app/admin/companies.rb , replace everything with:

In /app/admin/users.rb , replace everything with:

Now you should be able to visit http://localhost:3000/admin/dashboard and navigate the “Companies” and “Users” tabs on top.

Whew! That was a lot! We created an Identity provider that can manage Users, Companies, Applications with five very popular gems.

I know I have covered a lot of grounds without much explanations. Please refer to the gem’s github page for more info. They are pretty well documented. There are tons of options and customization that there is noway to cover them all.

Anyway, I hope this could help anyone out there in someway. You can find the github repos at:
https://github.com/hszeto/client-one
https://github.com/hszeto/client-two
https://github.com/hszeto/identity

Feel free to play around with the demo apps:
username: user@example.com
password: password
https://client-one.herokuapp.com/

https://client-two.herokuapp.com/
https://identityservice.herokuapp.com/