Skip to content

Instantly share code, notes, and snippets.

@traumverloren
Last active March 10, 2022 10:27
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save traumverloren/eb7c27e1e2780240c13f to your computer and use it in GitHub Desktop.
Save traumverloren/eb7c27e1e2780240c13f to your computer and use it in GitHub Desktop.
doorkeeper config for redirect back to client app after login with oauth2 provider
#########################
# config/initializers/doorkeeper.rb
#########################
Doorkeeper.configure do
# Change the ORM that doorkeeper will use.
# Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
orm :active_record
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
User.find_by_id(session[:user_id]) || redirect_to(new_session_url(return_to: request.fullpath))
end
# Provide support for an owner to be assigned to each registered application (disabled by default)
# Optional parameter :confirmation => true (default false) if you want to enforce ownership of
# a registered application
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
enable_application_owner :confirmation => true
# In this flow, a token is requested in exchange for the resource owner credentials
resource_owner_from_credentials do |routes|
user = User.find_by_email(params[:email])
user if user && user.authenticate(params[:password])
end
# Allow non https redirects
force_ssl_in_redirect_uri false
# You'll receive the access token back in the response
# {"access_token":"b1b218369cc52a47c891feaadec8b5d792288aafe02c11c2d835548f350f574d",
# "token_type":"bearer",
# "expires_in":7200,
# "created_at":1425474562}
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
# admin_authenticator do
# # Put your admin authentication logic here.
# # Example implementation:
# Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
# end
# Authorization Code expiration time (default 10 minutes).
# authorization_code_expires_in 10.minutes
# Access token expiration time (default 2 hours).
# If you want to disable expiration, set this to nil.
access_token_expires_in 30.days
# Reuse access token for the same resource owner within an application (disabled by default)
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
# reuse_access_token
# Issue access tokens with refresh token (disabled by default)
use_refresh_token
# Define access token scopes for your provider
# For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
# default_scopes :public
# optional_scopes :write, :update
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
# falls back to the `:client_id` and `:client_secret` params from the `params` object.
# Check out the wiki for more information on customization
# client_credentials :from_basic, :from_params
# Change the way access token is authenticated from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
# falls back to the `:access_token` or `:bearer_token` params from the `params` object.
# Check out the wiki for more information on customization
# access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
# Change the native redirect uri for client apps
# When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider
# The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
# (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
#
# native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
# 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 all the four grant flows.
#
# grant_flows %w(authorization_code implicit password client_credentials)
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with trusted a application.
# skip_authorization do |resource_owner, client|
# client.superapp? or resource_owner.admin?
# end
# WWW-Authenticate Realm (default "Doorkeeper").
# realm "Doorkeeper"
# Allow dynamic query parameters (disabled by default)
# Some applications require dynamic query parameters on their request_uri
# set to true if you want this to be allowed
# wildcard_redirect_uri false
end
# Extend Doorkeeper models
Doorkeeper::Application.send :include, ApplicationExtension
Doorkeeper.configuration.token_grant_types << "password"
#########################
# sessions_controller.rb
#########################
class SessionsController < ApplicationController
def new
session[:return_to] = params[:return_to]
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to session[:return_to] || root_url, notice: "Logged in!"
session.delete(:return_to)
else
flash.now.alert = "Invalid password or email"
render "new"
end
end
end
@jiggneshhgohel
Copy link

With the version 4.2.0 of Devise I was using there following a convention set by Devise for the session variable the need to do any modifications in the SessionsController can be avoided. Following that convention I updated my config to following and it worked without any issues:

Doorkeeper.configure do
  ...

   resource_owner_authenticator do |routes|
    if current_user
      current_user
    else
      # Refer the HERE document at the bottom on why this session variable is being set.
      session[:user_return_to] = request.fullpath
      redirect_to(new_user_session_url)
    end
  end
  ....
end

=begin

 Why this specific session variable? Because Devise looks for this name
 Refer `after_sign_in_path_for(resource_or_scope)` method
 implementation and documentation at

  https://github.com/plataformatec/devise/blob/v4.2.0/lib/devise/controllers/helpers.rb#L213

 and you should get it why.

  https://github.com/plataformatec/devise/blob/v4.2.0/lib/devise/controllers/store_location.rb#L17
  https://github.com/plataformatec/devise/blob/v4.2.0/lib/devise/controllers/store_location.rb#L54

 And `after_sign_in_path_for` method should come into picture when Client-app
 sends an authorization request to this Provider-app and the user is not
 found to be logged-in on the Provider-app. So Devise should throw
 the user to the Sign-in page and after successful sign-in user should
 be redirected to the Client-app. So we are storing the Client-app's
 path here in a session key supported by Devise in out-of-the-box fashion
 for handling redirects to original page after signing-in.

 So just setting this session variable Devise should handle the redirect
 to Client-app AFTER SUCCESSFUL SIGN-IN without any additional changes
 in it's SessionsController implementation.

 References:
  https://dev.mikamai.com/2015/02/11/oauth2-on-rails/
  https://stackoverflow.com/a/21632889/936494

 Note that if this session variable is NOT set then what would happen is
 that when user from Client-app sends authorization request and user
 is not NOT found logged-in by Devise on this Provider-app
 the Provider-app display's Sign Page but after successful login
 Provider-app doesn't redirect the logged-in user to Client-app.

 However that's not the case when user is found logged-in on this Provider-app
 In that case Provider-app displays the Authorize/Deny page facilitated
 by Doorkeeper. Clicking either on Authorize or Deny button the redirect
 happens!


 THIS SHOULD WORK ASSUMING YOU HAVE NOT ALREADY OVERRIDDEN

   after_sign_in_path_for(resource)`

 in your ApplicationController like following


   def after_sign_in_path_for(resource)
     user_home_path
   end


 If that is the case then setting this session variable should have NO EFFECT.
 And to fix that you will need to do this

    def after_sign_in_path_for(resource)
      stored_location_for(resource) || user_home_path
    end

=end

Hope that helps.

Thanks.

@sagar-ranglani
Copy link

@jiggneshhgohel this really helped a lot! Thank you for posting this with such clarity! :-)

Now, Where I'm stuck at is how would I delete the user session when the user clicks logout! We don't have a logout URL with the doorkeeper.

The user should be logged out and when the user logs in again (I'm using identity.launchWebAuthFlow) He should be prompted to enter login details again. Right now, resource_owner_authenticator always has current_user present

 resource_owner_authenticator do |routes|
    if current_user
      current_user
    else
      session[:user_return_to] = request.fullpath
      redirect_to(new_user_session_url)
    end
  end

When I make API calls with the token, the current_user is nil (i.e. warden.authenticated?(:user) is false) so warden.logout does not work when I try to delete the session through API calls.

This SO question discusses that thing to an extent and here the user is logged out right before returning the token. I want to do this (warden.logout) on users sign_out action. Can you help me do this?

In a nutshell, I want to logout the user session which we created during resource_owner_authenticator block when the user clicks logout. (I'm using implicit grant flow for a chrome extension to our API, which is the OAuth provider with Doorkeeper). Any help would be highly appreciated. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment