doorkeeper is a gem for providing OAuth provider function. For example, when you create a public API, you can issue an access token by authorizing it with OAuth 2.0 and issuing a token.
This time, we will actually install doorkeeper and confirm that it functions as an OAuth provider, but detailed settings are omitted.
Please note that it is necessary to make more careful settings such as routing settings, screen customization, and doorkeeper.rb
customization for applications that are actually developed in business.
We introduced doorkeeper with reference to the following. The doorkeeper gem is well documented and should be easy to deploy. https://github.com/doorkeeper-gem/doorkeeper https://github.com/doorkeeper-gem/doorkeeper/wiki https://doorkeeper.gitbook.io/guides/
This time, we will proceed assuming that the rails application in the following environment already exists.
rails: 6.0.3.4
ruby: 2.7.1
Other gem:
devise(4.7.3)
This time, prepare the user table in advance and manage the login area with devise gem. The schema of the user table is as follows. (This is a simple table created for the introduction of Doorkeeper)
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
# Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
t.string :fullname
t.string :fullname_ja
t.boolean :admin, null: false, default: false
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
Add the following to your Gemfile and bundle install.
gem 'doorkeeper'
bundle install
At the moment (2020/12/01), the following versions have been installed.
doorkeeper (5.4.0)
Install doorkeeper.
rails generate doorkeeper:install
Execution result
create config/initializers/doorkeeper.rb
create config/locales/doorkeeper.en.yml
route use_doorkeeper
===============================================================================
There is a setup that you need to do before you can use doorkeeper.
Step 1.
Go to config/initializers/doorkeeper.rb and configure
resource_owner_authenticator block.
Step 2.
Choose the ORM:
If you want to use ActiveRecord run:
rails generate doorkeeper:migration
And run
rake db:migrate
Step 3.
That's it, that's all. Enjoy!
===============================================================================
The following two files will be created and a routing for using the doorkeeper will be generated.
config/initializers/doorkeeper.rb
config/locales/doorkeeper.en.yml
Then run the following to create a migration file for the OAuth Applications, Access Grants, and Access Tokens tables.
bundle exec rails generate doorkeeper:migration
Execution result
create db/migrate/20201201064749_create_doorkeeper_tables.rb
The db/migrate/yyyymmddhhmmss_create_doorkeeper_tables.rb
file will be created, so edit it.
This time, we will set the user model to be the resource owner.
# frozen_string_literal: true
class CreateDoorkeeperTables < ActiveRecord::Migration[6.0]
def change
create_table :oauth_applications do |t|
t.string :name, null: false
t.string :uid, null: false
t.string :secret, null: false
# Remove `null: false` if you are planning to use grant flows
# that doesn't require redirect URI to be used during authorization
# like Client Credentials flow or Resource Owner Password.
t.text :redirect_uri, null: false
t.string :scopes, null: false, default: ''
t.boolean :confidential, null: false, default: true
t.timestamps null: false
end
add_index :oauth_applications, :uid, unique: true
create_table :oauth_access_grants do |t|
t.references :resource_owner, null: false
t.references :application, null: false
t.string :token, null: false
t.integer :expires_in, null: false
t.text :redirect_uri, null: false
t.datetime :created_at, null: false
t.datetime :revoked_at
t.string :scopes, null: false, default: ''
end
add_index :oauth_access_grants, :token, unique: true
add_foreign_key(
:oauth_access_grants,
:oauth_applications,
column: :application_id
)
create_table :oauth_access_tokens do |t|
t.references :resource_owner, index: true
# Remove `null: false` if you are planning to use Password
# Credentials Grant flow that doesn't require an application.
t.references :application, null: false
# If you use a custom token generator you may need to change this column
# from string to text, so that it accepts tokens larger than 255
# characters. More info on custom token generators in:
# https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
#
# t.text :token, null: false
t.string :token, null: false
t.string :refresh_token
t.integer :expires_in
t.datetime :revoked_at
t.datetime :created_at, null: false
t.string :scopes
# The authorization server MAY issue a new refresh token, in which case
# *the client MUST discard the old refresh token* and replace it with the
# new refresh token. The authorization server MAY revoke the old
# refresh token after issuing a new refresh token to the client.
# @see https://tools.ietf.org/html/rfc6749#section-6
#
# Doorkeeper implementation: if there is a `previous_refresh_token` column,
# refresh tokens will be revoked after a related access token is used.
# If there is no `previous_refresh_token` column, previous tokens are
# revoked as soon as a new access token is created.
#
# Comment out this line if you want refresh tokens to be instantly
# revoked after use.
t.string :previous_refresh_token, null: false, default: ""
end
add_index :oauth_access_tokens, :token, unique: true
add_index :oauth_access_tokens, :refresh_token, unique: true
add_foreign_key(
:oauth_access_tokens,
:oauth_applications,
column: :application_id
)
#user is resource_Be the owner and uncomment it.
# Uncomment below to ensure a valid reference to the resource owner's table
# add_foreign_key :oauth_access_grants, <model>, column: :resource_owner_id
# add_foreign_key :oauth_access_tokens, <model>, column: :resource_owner_id
↓↓↓↓↓
add_foreign_key :oauth_access_grants, :users, column: :resource_owner_id
add_foreign_key :oauth_access_tokens, :users, column: :resource_owner_id
end
end
Execute migration.
rails db:migrate RAILS_ENV=development
The following three tables are created.
oauth_applications
oauth_access_grants
oauth_access_tokens
Next, edit the doorkeeper configuration file config/initializers/doorkeeper.rb
.
The setting method is
https://doorkeeper.gitbook.io/guides/ruby-on-rails/configuration
I will proceed with reference to.
Only the edited part is described.
# frozen_string_literal: true
Doorkeeper.configure do
~ ~ Abbreviation ~ ~
#This block is called to see if the resource owner is authenticated.
#I'm using devise this time
# https://doorkeeper.gitbook.io/guides/ruby-on-rails/configuration
#Correct it referring to.
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
raise "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}"
# Put your resource owner authentication logic here.
# Example implementation:
# User.find_by(id: session[:user_id]) || redirect_to(new_user_session_url)
end
↓↓↓↓
#Modify as follows.
resource_owner_authenticator do
current_user || warden.authenticate!(scope: :user)
end
#Here you can control access to the oauth application.
#This time, I added the admin flag to the users table so that I can access it only when the admin flag is set.
#Remove the commented out part.
#If a user other than admin accesses it, a 403 Forbidden response is returned.
# If you didn't skip applications controller from Doorkeeper routes in your application routes.rb
# file then you need to declare this block in order to restrict access to the web interface for
# adding oauth authorized applications. In other case it will return 403 Forbidden response
# every time somebody will try to access the admin web interface.
#
# admin_authenticator do
# # Put your admin authentication logic here.
# # Example implementation:
#
# if current_user
# head :forbidden unless current_user.admin?
# else
# redirect_to sign_in_url
# end
# end
↓↓↓↓
admin_authenticator do
# Put your admin authentication logic here.
# Example implementation:
if current_user
head :forbidden unless current_user.admin?
else
redirect_to new_user_session_url
end
end
~ ~ Abbreviation ~ ~
# access_token_expires_in option
#This is the setting for the expiration date of the access token.
#2 hours by default. If you want to change it, uncomment it and edit it.
#If you want to disable the expiration date, set this to "nil".
#
#This time it will be changed to 24 hours.
# Access token expiration time (default: 2 hours).
# If you want to disable expiration, set this to `nil`.
#
# access_token_expires_in 2.hours
access_token_expires_in 24.hours
~ ~ Abbreviation ~ ~
# scope
#Provider access token scope definition
#For more information, please visit:
# https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes
#
#This time
# default_scopes :public and
# optional_scopes :write
#Is set.
# Define access token scopes for your provider
# For more information go to
# https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes
#
# default_scopes :public
# optional_scopes :write, :update
↓↓↓↓
default_scopes :public
optional_scopes :write
# enforce_configured_scopes option
# 「default_"scopes" and "optional"_Prohibits the creation and update of applications in any scope that is not in "scopes".
# (Not prohibited by default)
#
#Uncomment it for use.
# Forbids creating/updating applications with arbitrary scopes that are
# not in configuration, i.e. +default_scopes+ or +optional_scopes+.
# (disabled by default)
#
# enforce_configured_scopes
↓↓↓↓
enforce_configured_scopes
# force_ssl_in_redirect_uri option
#Forces the HTTPS protocol with uris for non-native redirects.
# (Enabled by default except in the development environment)
#OAuth2 delegates communication security to the HTTPS protocol, so it's wise to enable it.
# proc, lambda,Callable objects such as block
#Can be used to enable conditional checking
# (For example, allow non-SSL redirects to localhost)
#
#Make the following settings for use this time.
# Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
# by default in non-development environments). OAuth2 delegates security in
# communication to the HTTPS protocol so it is wise to keep this enabled.
#
# Callable objects such as proc, lambda, block or any object that responds to
# #call can be used in order to allow conditional checks (to allow non-SSL
# redirects to localhost for example).
#
# force_ssl_in_redirect_uri !Rails.env.development?
#
# force_ssl_in_redirect_uri { |uri| uri.host != 'localhost' }
↓↓↓↓
#development,In the test environment, non-SSL is allowed, otherwise only SSL is allowed.
force_ssl_in_redirect_uri !(Rails.env.development? || Rails.env.test?)
#Like Client Credentials and Resource Owner Password Credentials
#If Doorkeeper is configured to use OAuth grant flow without URI,
#Allows the application to have an empty redirect URI.
#This option is set to on by default and checks the set grant type,
#Database table "oauth_"redirect" of "applications"_"NOT NULL" constraint from "uri" column
#You need to remove it manually.
#Allow to completely disable this feature_blank_redirect_Do you want to uncomment the uri false?
#You can define custom checks.
#
#This time redirect_Uncomment it because it doesn't allow uri to be null.
# Allows to set blank redirect URIs for Applications in case Doorkeeper configured
# to use URI-less OAuth grant flows like Client Credentials or Resource Owner
# Password Credentials. The option is on by default and checks configured grant
# types, but you **need** to manually drop `NOT NULL` constraint from `redirect_uri`
# column for `oauth_applications` database table.
#
# You can completely disable this feature with:
#
# allow_blank_redirect_uri false
#
# Or you can define your custom check:
#
# allow_blank_redirect_uri do |grant_flows, client|
# client.superapp?
# end
↓↓↓↓
allow_blank_redirect_uri false
#Specifies what grant flows to enable in an array of strings.
#The valid strings and valid flows are as follows.
#
#This time authorization_Since only code is allowed, write as follows.
# Specify what grant flows are enabled in array of Strings. The valid
# strings and the flows they enable are:
#
# "authorization_code" => Authorization Code Grant Flow
# "implicit" => Implicit Grant Flow
# "password" => Resource Owner Password Credentials Grant Flow
# "client_credentials" => Client Credentials Grant Flow
#
# If not specified, Doorkeeper enables authorization_code and
# client_credentials.
#
# implicit and password grant flows have risks that you should understand
# before enabling:
# http://tools.ietf.org/html/rfc6819#section-4.4.2
# http://tools.ietf.org/html/rfc6819#section-4.4.3
#
# grant_flows %w[authorization_code client_credentials]
↓↓↓↓
grant_flows %w[authorization_code]
end
** (*) Create two accounts with the admin flag and one without the admin flag in advance. ** **
> rails s
Start local Server with.
http://localhost:3000/users/sign_in
Go to and log in with an account that does not have the ** admin flag **
http://localhost:3000/oauth/applications
Make sure that you cannot access with status 403 when you access.
From now on, log in with an account that has the ** admin flag **
http://localhost:3000/oauth/applications
When you access, you will see the following screen.
Click the New Application button We will create an OAuth application.
Name: ExampleApp
Redirect URI: http://localhost:3001/callback
Confidential: ON
Scopes: public write
Set and click Submit.
You should see an OAuth application created and a screen like the one below.
If you look at the actually created data with the rails console, you can see that the following data exists.
irb(main):003:0> Doorkeeper::Application.all
=> #<ActiveRecord::Relation [#<Doorkeeper::Application id: 1, name: "ExampleApp", uid: "4y5OWnkpJM9wHwk6F9LHTKBB4Daz0x_3cwDdeqCcQD4", secret: "7Ufjfqcdt36rSkvx8PLHVRSHh-IL2KLk_9v1Tp4XBFs", redirect_uri: "http://localhost:3001/callback", scopes: "public write", confidential: true, created_at: "2020-12-14 17:44:08", updated_at: "2020-12-14 17:44:08">]>
irb(main):009:0>
Next, in order to test the issuance of the access token, the Doorkeeper Client is required, so use the ↓ client.
https://github.com/Nobuo-Hirai/sample_oauth_client
Follow the steps below to set up.
git clone [email protected]:Nobuo-Hirai/sample_oauth_client.git
cd sample_oauth_client
bundle config set --local path 'vendor/bundle'
bundle install
# .env.Create a development file.
touch .env.development
# .env.Describe the following contents in the development file.
#Describe the UID, SECRET, REDIRECT URI, etc. of the OAuth application created by the OAuth provider earlier.
## ID for your app registered at the provider
CLIENT_ID=4y5OWnkpJM9wHwk6F9LHTKBB4Daz0x_3cwDdeqCcQD4
## Secret
CLIENT_SECRET=7Ufjfqcdt36rSkvx8PLHVRSHh-IL2KLk_9v1Tp4XBFs
## URL to the provider
SITE=http://localhost:3000/
AUTHORIZE_URL=oauth/authorize
CALLBACK_URI=http://localhost:3001/callback
OAUTH_PROVIDER_URL=http://localhost:3000/oauth/authorize
TOKEN_URL=oauth/token
SCOPE=public write
#Start on port 3001.
rails s -p 3001
http://localhost:3001 When you access, the screen below will be displayed. Click [Authorize].
http://localhost:3000/ You should see the authorization screen. This means whether you agree to issue an access token with the authority [public write]. Click Authorize.
http://localhost:3001/callback I think that the screen of is displayed and the access token is displayed.
After clicking [Authorize] on the authorization screen, the authorization code parameter is returned to the callback URL, so the access token is obtained based on that.
#Access from authorization code_Get token
def callback
client = OAuth2::Client.new(
ENV["CLIENT_ID"],
ENV["CLIENT_SECRET"],
site: ENV["SITE"],
authorize_url: ENV["AUTHORIZE_URL"],
token_url: ENV["TOKEN_URL"],
)
@access_token = client.auth_code.get_token(
params[:code],
redirect_uri: ENV["CALLBACK_URI"],
)
end
When checking the actually created Access Grant and Access Token on the rails console, you can check it as follows.
irb(main):010:0> Doorkeeper::AccessGrant.all
=> #<ActiveRecord::Relation [#<Doorkeeper::AccessGrant id: 1, resource_owner_id: 1, application_id: 1, token: "j0UY2MVee0mW9RmjVhPTZ6hY7yzw44nM5YOS51ZhxUk", expires_in: 600, redirect_uri: "http://localhost:3001/callback", created_at: "2020-12-14 17:54:24", revoked_at: "2020-12-14 17:54:24", scopes: "public write">]>
irb(main):011:0> Doorkeeper::AccessToken.all
=> #<ActiveRecord::Relation [#<Doorkeeper::AccessToken id: 1, resource_owner_id: 1, application_id: 1, token: "l2wm7_P1Fk2ZZEAI5jhEFWfsJYUzbZYk_QcpWKZCSFU", refresh_token: nil, expires_in: 86400, revoked_at: nil, created_at: "2020-12-14 17:54:24", scopes: "public write", previous_refresh_token: "">]>
I think the hurdle to introduce the doorkeeper gem is low because it has a lot of documents.
However, there are many options that can be set in doorkeeper.rb
, so it takes a long time to check all of them.
The setting in doorkeeper.rb
is the point of this gem, so I will try various things.
Recommended Posts