[Awesome Ruby Gem] Use doorkeeper gem to make your Rails project as an OAuth 2 provider
doorkeeper
Doorkeeper is a gem (Rails engine) that makes it easy to introduce OAuth 2 provider functionality to your Ruby on Rails or Grape application.
Supported features:
-
The OAuth 2.0 Authorization Framework
-
Authorization Code Flow
-
Access Token Scopes
-
Refresh token
-
Implicit grant
-
Resource Owner Password Credentials
-
Client Credentials
-
-
OAuth 2.0 Token Revocation
-
OAuth 2.0 Token Introspection
-
OAuth 2.0 Threat Model and Security Considerations
-
OAuth 2.0 for Native Apps
-
Proof Key for Code Exchange by OAuth Public Clients
Installation
You can install it as a gem:
1 | gem install doorkeeper |
or add it into a Gemfile (Bundler):
1 | # Gemfile |
Then, run bundle install
.
1 | bundle install |
Usage
Generator
After that, you need to generate relevant files with:
1 | bundle exec rails generate doorkeeper:install |
This will introduce three changes:
-
A new initializer in
config/initializers/doorkeeper.rb
-
Add doorkeeper’s routes to
config/routes.rb
-
Locale files in
config/locales/doorkeeper.en.yml
Migrations
To generate appropriate tables, run:
1 | bundle exec rails generate doorkeeper:migration |
This migration will create all necessary tables for oAuth2 Applications - https://doorkeeper.gitbook.io/guides/concepts/application, Access Grants - https://doorkeeper.gitbook.io/guides/concepts/access-grant, and Access Tokens - https://doorkeeper.gitbook.io/guides/concepts/access-token. See the database design - https://doorkeeper.gitbook.io/guides/internals/database-design for more details.
NOTE: If using UUIDs instead of integer IDs, see Using PostgreSQL UUIDs as primary keys with Doorkeeper - https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-PostgreSQL-UUIDs-as-primary-keys-with-Doorkeeper for changes you will need to make to your migration.
Integrating with existing User Model
Before executing the migration, you may want to add foreign keys to doorkeeper’s tables to ensure data integrity. Go to the migration file and uncomment the lines below:
1 | # db/migrate/20190324080634_create_doorkeeper_tables.rb |
Now you’re ready to run the migrations:
1 | bundle exec rake db:migrate |
As the next step, you may want to add associations to your model. If you skip this step, you’ll encounter ActiveRecord::InvalidForeignKeyerror
when you try to destroy the User that has associated access grants or access tokens.
1 | # app/models/user.rb |
Routes
The installation script will also automatically add the Doorkeeper routes into your app:
1 | config/routes.rb |
This will mount following routes:
1 | bundle exec rails routes |
At this point on the guide, we’re not going to change the routes. For more information on how to customize them, check out this page on the wiki - https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes.
Configuration
Before you’re able to use Doorkeeper, you need to configure how resource owners (users) can be authenticated and who can manage such applications.
Resource Owner Authentication
This configuration should do two things:
-
Return the user is currently authenticated
-
Redirect the user to the authentication page
If you’re using devise, one option is to write the following:
1 | # config/initializers/doorkeeper.rb |
The block above runs in the context of your application so you have access to your models, session and routes helpers. However, it is not run in the context of the ApplicationController which means that it doesn’t have access to the methods defined over there.
You may want to check other ways of authentication - https://github.com/doorkeeper-gem/doorkeeper/wiki/Authenticating-using-Clearance-or-DIY here.
Application Management Authentication
By default, the applications list in /oauth/applications
is unavailable. To let users see and manage all applications, you should configure admin_authenticator
block:
1 | config/initializers/doorkeeper.rb |
The block follows the same rules as resource_owner_authenticator
block.
Note: the application list is just a scaffold. It’s highly recommended to either customize the controller used by the list or skip the controller all together. For more information see the page in the wiki - https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes.
Scopes
For this guide let’s create two scopes: read and write. Applications authorized with the read scope will be able to read public data from our API. The write scope will let applications change data in our API.
Go to doorkeeper’s initializer and add:
1 | # config/initializers/doorkeeper.rb |
The last line with enforce_configured_scopes
ensures applications to be able to ask only for configured scopes defined in default_scopes
and optional_scopes
.
Doorkeeper::Application
In order to add Applications for authorizing, you will need to create them via console:
1 | Doorkeeper::Application.create(name: "MyApp", redirect_uri: "urn:ietf:wg:oauth:2.0:oob", scopes: ["read", "write"]) |
Securing the API
To protect your controllers (usual one or ActionController::API
) with OAuth, you just need to setup before_actions
specifying the actions you want to protect. For example:
1 | class Api::V1::ProductsController < Api::V1::ApiController |
You can pass any option before_action accepts, such as if
, only
, except
, and others.
Authenticated resource owner
If you want to return data based on the current resource owner, in other words, the access token owner, you may want to define a method in your controller that returns the resource owner instance:
1 | class Api::V1::CredentialsController < Api::V1::ApiController |
In this example, we’re returning the credentials (me.json) of the access token owner.
API Mode
By default Doorkeeper uses full Rails stack to provide all the OAuth 2 functionality with additional features like administration area for managing applications. By the way, starting from Doorkeeper 5 you can use API mode for your API only Rails 5 applications. All you need is just to configure the gem to work in desired mode:
1 | config/initializers/doorkeeper.rb |
Keep in mind, that in this mode you will not be able to access Applications or Authorized Applications controllers because they will be skipped. CSRF protections (which are otherwise enabled) will be skipped, and all the redirects will be returned as JSON response with corresponding locations.
Authorization Code Flow
Authorization code is probably the most used flow. It basically consists of an exchange of an authorization token for an access token. For more detailed info, check out the RFC spec here
The first step is to register your client app.
Registering the client
Once you have doorkeeper up and running, set up a new client in /oauth/applications/new
. For testing purposes, you should fill in the redirect URI field with urn:ietf:wg:oauth:2.0:oob
. This will tell doorkeeper to display the authorization code instead of redirecting to a client application (that you don’t have now).
You can change this behaviour by changing the native_redirect_uri
config in the doorkeeper initializer.
Requesting authorization
To request the authorization token, you should visit the /oauth/authorize
endpoint. You can do that either by clicking in the link to the authorization page in the app details or by visiting manually the URL:
Once you are there, you should sign in and click on Authorize and Access grant.
Requesting the access token
To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client:
1 | parameters = 'client_id=THE_ID&client_secret=THE_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=urn:ietf:wg:oauth:2.0:oob' |
You can now make requests to the API with the access token returned.
Client Credentials flow
The Client Credentials flow is probably the most simple flow of OAuth 2 flows. The main difference from the others is that this flow is not associated with a resource owner.
One usage of this flow would be retrieving client statistics for example. Since the access token would be connected to the client only, the access token won’t have access to private user data for example.
Enabling the grant flow
In your Doorkeeper configuration initializer, have a line like this:
1 | # add other flows to this array if you want more to be enabled, e.g., %w{authorization_code implicit password} |
Usage
To get an access token from client credentials flow, you have to do a post to /oauth/token
endpoint:
1 | POST /oauth/token |
The Authorization header includes the encoded credentials for the client. For more information and options on how authenticate clients, check this page in the wiki.
In ruby, it would be something like this:
1 | require 'rest-client' |
Notice that in this case we used client_id/secret on parameters instead of using the encoded header.
After that you’ll have the access token in the response:
1 | token = JSON.parse(response)["access_token"] |
And then, you can request access to protected resources that do not require a resource owner:
1 | RestClient.get 'http://localhost:3000/api/v1/profiles.json', { 'Authorization' => "Bearer #{token}" } |
That’s everything.
References
[2] Doorkeeper Guides - doorkeeper - https://doorkeeper.gitbook.io/guides/
[3] doorkeeper | RubyGems.org | your community gem host - https://rubygems.org/gems/doorkeeper/