Overriding Doorkeeper refresh token response - ruby-on-rails

I am trying to also send the user details in the refresh token response using Doorkeeper (I am using it on API only). I tried calling the Doorkeeper::TokensController#create method in one of my other controllers, but it does not work. As a workaround, I created a new controller that inherits from Doorkeeper::TokensController and in the create method I try to do render json: { tokens: JSON.parse(super), user: the_instance_i_want }, but this would do render twice, as this method also has a render.
Is there any other way to solve this? I would also want to refresh the user data when refreshing the token.

You should render the same response for first jwt issue and refresh's. Anyway you can override TokenResponse body method:
module Doorkeeper
module OAuth
class TokenResponse
def body
{
"access_token" => token.plaintext_token,
"token_type" => token.token_type,
"expires_in" => token.expires_in_seconds,
"refresh_token" => token.plaintext_refresh_token,
"scope" => token.scopes_string,
"created_at" => token.created_at.to_i,
"CUSTOM1" => YOUR_IMPLEMENTATION,
# custom
response_code: I18n.t('custom.status_code.ok'),
response_message: I18n.t('custom.success.default')
}.reject { |_, value| value.blank? }
end
end
end
end

Related

Callbacks in Ruby on Rails

Using the 'oauth2' gem and a Heroku server, I have managed to create a client object and redirect the user to the login site:
client = OAuth2::Client.new(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
:authorize_url => "/oauth/authorize",
:token_url => "/oauth/token",
:site => "https://connect.xxxxxxxxxx.com")
redirect_to(client.auth_code.authorize_url(:redirect_uri => 'https://xxxxx.herokuapp.com/callback'))
The browser afterwards redirects itself to the callback link as intended, something like:
https://xxxxx.herokuapp.com/callback?code=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I need to access the authorization code to then send a post request for the access token and refresh token, but being totally new to Ruby and Ruby on Rails, I am not sure how to get the callback and parse the code out. All of the dozen tutorials/documentation I've researched just mention that the authorization code should be 'magically obtained,' but I'm not sure how exactly that works explicitly. I tried creating a 'callback' controller and view to no avail - is there something missing in the routes files possibly? Help is much appreciated!
Your CallbackController will start to look like this maybe:
class CallbackController < ApplicationController
def index
access_token = client.auth_code.get_token(params[:code], redirect_uri: 'https://xxxxx.herokuapp.com/callback')
# Now you have an OAuth2::AccessToken object that you can either use to:
# - make direct requests to the API
# - or access access_token.token, access_token.refresh_token, access_token.expires_at, access_token.expires_in and store those
# somewhere for later use
# http://www.rubydoc.info/github/intridea/oauth2/OAuth2/AccessToken
end
private
def client
#client ||= OAuth2::Client.new(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authorize_url: "/oauth/authorize",
token_url: "/oauth/token",
site: "https://connect.xxxxxxxxxx.com"
)
end
end

LinkedIn API Ruby/OAuth2 integration

I'm trying to write an integration with the LinkedIn API for my Rails app using OAuth2 but I'm having a hard time figuring out how to make authenticated API calls. I can't seem to find any resources on the topic that don't rely on the linkedin gem. I already have that gem installed in my app, but need to make some API calls that are not possible using that gem.
What I want to do is set up a controller with some endpoints that my Angular client can ping. When the client pings the controller, it should in turn ping the LinkedIn API. Once it gets a response it will perform some operations to store the information in the database and then pass it along to the Angular client.
I found a single code sample in Ruby from LinkedIn, which I'm attempting to modify into a controller for my app. Here is my controller:
class Employers::LinkedinApiController < ApplicationController
require 'net/http'
layout 'employers/layouts/application'
before_action :authorize_viewer
skip_after_filter :intercom_rails_auto_include
REDIRECT_URI = 'http://app.mysite.local:3000/linkedin/callback'
STATE = SecureRandom.hex(15)
CLIENT = parameterize(client.auth_code.get_token(STATE, redirect_uri: REDIRECT_URI))
def get_update(update_key=nil)
company = current_employer.company
update_key = company.social_media_posts.where("linkedin IS true AND linkedin_id IS NOT NULL").last.linkedin_id
page_id = company.linkedin.page_id
end_point = "https://api.linkedin.com/v1/companies/#{page_id}/updates/key=#{update_key}?format=json"
access_token = OAuth2::AccessToken.new(client, CLIENT['token'], {
:mode => :header,
:header_format => 'Bearer %s'
})
response = access_token.get(end_point)
# Do something with the response to save it to the database
# Pass response on to Angular client
respond_to do
format.json { render json: response }
end
end
private
#Instantiate your OAuth2 client object
def client
OAuth2::Client.new(
ENV['LINKEDIN_APP_ID'],
ENV['LINKEDIN_APP_SECRET'],
authorize_url: "/uas/oauth2/authorization?response_type=code",
token_url: "/uas/oauth2/accessToken",
site: "https://www.linkedin.com"
)
end
def parameterize(string)
parameters = {}
string.split('?')[-1].split('&').each do |param|
parameters[param.split('=')[0]] = param.split('=')[-1]
end
parameters
end
def authorize_viewer
if !employer_signed_in?
redirect_to new_employer_session_path
end
end
end
In the code sample from LinkedIn, the only way to get data from the accept method is to ping the authorize method, whose callback points to the accept method. What I can't figure out is how I can ping the get_update method in my controller and make an authenticated API call, without having to set up an authorize method that calls back to get_update.
How can I do this?

omniauth-dropbox and dropbox-sdk, can they work together

Since I saw that the omniauth-dropbox gem, was made to:
Authenticate to the Dropbox REST API (v1).
I was happy i wouldn't need to develop all the redirections the OAuth implies.
But I can't find a way to make them work together :(
The omniauth-dropbox gem, works fine, alone, I get authenticated and stuff.
But what to save from the callback, so the dropbox-sdk would understand the user is authenticated?
How to do so the session.get_access_token would be automatically handled by omniauth-dropbox?
CODE
def dropbox
session = DropboxSession.new(MULTIAPI_CONFIG['dropbox']['appKey'], MULTIAPI_CONFIG['dropbox']['appSecret'])
session.get_request_token
authorize_url = session.get_authorize_url('myurl/auth/dropbox/callback')
print authorize_url
session.get_access_token
client = DropboxClient.new(session, ACCESS_TYPE)
object = client.metadata('/')
render :json => object
end
ERROR
Couldn't get access token. Server returned 401: Unauthorized.
It looks to me from https://github.com/intridea/omniauth/wiki like you can get env['omniauth.auth'] in your callback handler, and from there you can extract the credentials (token and secret). See https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema.
Once you have the token and secret, you should be able to call session.set_access_token to tell the Dropbox SDK what credentials to use.
So I finally ended writing everything using the dropbox-sdk
Controller
class DropboxController < ApplicationController
def new
db_session = DropboxSession.new(MULTIAPI_CONFIG['dropbox']['appKey'], MULTIAPI_CONFIG['dropbox']['appSecret'])
begin
db_session.get_request_token
rescue DropboxError => e
render template: "multi_api/home/refresh"
end
session[:dp_request_db_session] = db_session.serialize
# OAuth Step 2: Send the user to the Dropbox website so they can authorize
# our app. After the user authorizes our app, Dropbox will redirect them
# to our 'dp_callback' endpoint.
auth_url = db_session.get_authorize_url url_for(:dp_callback)
redirect_to auth_url
end
def destroy
session.delete(:dp_authorized_db_session)
render :json => checkAuth
end
def isAuthenticated
render :json => checkAuth
end
def checkAuth
val = {'isAuthenticated' => false}
begin
unless not session[:dp_authorized_db_session]
dbsession = DropboxSession.deserialize(session[:dp_authorized_db_session])
client = DropboxClient.new(dbsession, MULTIAPI_CONFIG['dropbox']['accessType'])
val = {'isAuthenticated' => true}
end
rescue DropboxError => e
val = {'isAuthenticated' => false}
end
val
end
def callback
# Finish OAuth Step 2
ser = session[:dp_request_db_session]
unless ser
render template: "multi_api/home/refresh"
return
end
db_session = DropboxSession.deserialize(ser)
# OAuth Step 3: Get an access token from Dropbox.
begin
db_session.get_access_token
rescue DropboxError => e
render template: "multi_api/home/refresh"
return
end
session.delete(:dp_request_db_session)
session[:dp_authorized_db_session] = db_session.serialize
render template: "multi_api/home/refresh"
end
end
routes
get 'dp/logout', :to => 'dropbox#destroy'
get 'dp/login', :to => 'dropbox#new'
get 'dp/callback', :to => 'dropbox#callback', :as => :dp_callback
get 'dp/isAuthenticated', :to => 'dropbox#isAuthenticated'
multi_api/home/refresh.html.erb
<script type="text/javascript">
function refreshParent()
{
window.opener.location.reload();
if (window.opener.progressWindow)
window.opener.progressWindow.close();
window.close();
}
refreshParent();
</script>
Requesting dropbox
dbsession = DropboxSession.deserialize(session[:dp_authorized_db_session])
#client = DropboxClient.new(dbsession, MULTIAPI_CONFIG['dropbox']['accessType'])
I open a new tab when i want to authenticate a user to dropbox, when this is done I automatically refresh the original page and close the tab (see: multi_pi/home/refresh.html.erb).
Since I do all of this in javascript I need to know if the user has been authenticated successfully, that's why I provided a route dp/isAuthenticated which will return a json string containing a 'isAuthenticated' key set at true or false.
Connected users are not saved into a the database, only into the session. So when the session is destroyed they will have to re-authenticate. If you want them to be save into the database, then, you should dig into #smarx solution, using omniauth will be far easier.
I wrote my code here as an exemple for those who only want to depend on the ruby dropbox-sdk
omniauth-dropbox v0.2.0 uses the oauth1 API
dropbox-sdk v1.5.1 uses the oauth1 API
dropbox-sdk v1.6.1 uses the oauth2 API
With omniauth-dropbox v0.2.0 and dropbox-sdk v1.5.1, the following code in the controller action that omniauth redirects to works for me:
auth_hash = request.env['omniauth.auth']
access_token = auth_hash[:credentials][:token]
access_token_secret = auth_hash[:credentials][:secret]
session = DropboxSession.new(DROPBOX_APP_ID, DROPBOX_ADD_SECRET)
session.set_access_token(access_token, access_token_secret)
client = DropboxClient.new(session)
puts client.account_info.inspect
There might be a way to get omniauth-dropbox v0.2.0 and dropbox-sdk v1.6.1 working, but I haven't found it.
As James says, the most recent version of dropbox-sdk uses oauth2, so instead of omniauth-dropbox you need to use the oauth2 strategy:
https://github.com/bamorim/omniauth-dropbox-oauth2
Then the access_token will be present in the initial oauth response (as auth.credentials.token), and you can do with it as you will in the omniauth_callbacks_controller. I store it in an Authentication object.
For all those of you still looking for a tutorial for performing basically everything dropbox does, here's a tutorial I wrote a while ago:
http://blog.jobspire.net/dropbox-on-rails-4-0-ajax-using-dropbox-sdk/#more-54
You're welcome!

Facebook Javascript SDK and OmniAuth

I have successfully integrated the OmniAuth Facebook login flow into my rails application on the server side. However, I am also trying to get this to work using the Facebook Javascript SDK on the client side and am running into some issues.
EDIT: THIS ISSUE ONLY SEEMS TO BE HAPPENING IN CHROME AND NOT IN SAFARI OR FIREFOX
Sessions Controller - This works on the server side flow
def create
auth = request.env['omniauth.auth']
#if an authorization does not exisit, it will create a new authorization record. it will also create a new user record if a user is not currently logged in
unless #auth = Authorization.find_from_hash(auth)
# Create a new user or add an auth to existing user, depending on
# whether there is already a user signed in.
#auth = Authorization.create_from_hash(auth, current_user)
#add the friends array to user record. as of now only doing this on the initial user create
#friends = []
FbGraph::User.me(#auth.user.authorization.facebook_token).fetch.friends.each do |t|
#friends << t.identifier
end
u = #auth.user
u.facebook_friends = #friends
u.save
end
#store a new auth token if needed (if the new token in the hash does not match the one stored in the database for authorization)
Authorization.check_if_new_auth_token_is_needed(auth)
# Log the authorizing user in.
self.current_user = #auth.user
redirect_to root_url
end
If I simply hit the /auth/facebook path, the user will be logged in
Routes
match '/auth/:provider/callback', :to => 'sessions#create'
Now on the homepage view I am trying to now run a client side flow login
Homepage View
<script>
$(function() {
$('a').click(function(e) {
e.preventDefault();
FB.login(function(response) {
if (response.authResponse) {
$('#connect').html('Connected! Hitting OmniAuth callback (GET /auth/facebook/callback)...');
// since we have cookies enabled, this request will allow omniauth to parse
// out the auth code from the signed request in the fbsr_XXX cookie
$.getJSON('/auth/facebook/callback', function(json) {
$('#connect').html('Connected! Callback complete.');
$('#results').html(JSON.stringify(json));
});
}
}, { scope: 'email,publish_stream' });
});
});
</script>
<p id="connect">
Connect to FB
</p>
<p id="results" />
I'm getting the following error in my log
{"error":{"message":"Missing authorization
code","type":"OAuthException","code":1}}
Basically, Omniauth is not picking up on the facebook signed request from the FB.login action (as https://github.com/mkdynamic/omniauth-facebook/blob/master/example/config.ru says it's supposed to).
Any ideas on how I can get this to work properly or what I may be doing incorrectly?
I realize this question is a year old, but I've run into this problem twice now so hopefully this helps someone.
There are two github threads related to this problem:
https://github.com/mkdynamic/omniauth-facebook/issues/73
and
https://github.com/intridea/omniauth-oauth2/issues/31
The source of the problem is the callback_phase method inside the omniauth-oauth2 gem:
if !options.provider_ignores_state && (request.params['state'].to_s.empty? || request.params['state'] != session.delete('omniauth.state'))
raise CallbackError.new(nil, :csrf_detected)
end
request.params['state'] and session['omniauth.state'] are both nil so the condition fails and a CallbackError exception is raised.
One solution is to set provider_ignores_state to true which circumvents the condition:
config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET'], {
strategy_class: OmniAuth::Strategies::Facebook,
provider_ignores_state: true,
}
As pointed out in the threads above it's not a permanent solution since it can leave you open to csrf attacks.
One other thing to note is that Chrome has issues writing cookies to localhost. Try using lvh.me as your domain (it resolves to 127.0.0.1).
More code to patch the problem isn't usually a great path, but if neither of those solutions work then you can always create your own handler and parse the Facebook cookies:
def handle_facebook_connect
#provider = 'facebook'
#oauth = Koala::Facebook::OAuth.new(ENV["FACEBOOK_ID"], ENV["FACEBOOK_SECRET"])
auth = #oauth.get_user_info_from_cookies(cookies)
# Get an extended access token
new_auth = #oauth.exchange_access_token_info(auth['access_token'])
auth['access_token'] = new_auth["access_token"]
# Use the auth object to setup or recover your user. The following is
# and example of how you might handle the response
if authentication = Authentication.where(:uid => auth['user_id'], :provider => #provider).first
user = authentication.user
sign_in(user, :event => :authentication)
end
# Redirect or respond with json
respond_to do |format|
format.html { redirect_to user }
format.json { render json: user }
end
end
Then you'll need to redirect to the 'handle_facebook_connect' method when you receive a connected response:
FB.Event.subscribe('auth.authResponseChange', function(response) {
if(response.status === 'connected'){
if(response.authResponse){
// Redirect to our new handler
window.location = '/handle_facebook_connect';
// Or make an ajax request as in the code in the original question:
// $.getJSON('/handle_facebook_connect', function(json) {
// $('#connect').html('Connected! Callback complete.');
// $('#results').html(JSON.stringify(json));
// });
}
} else if (response.status === 'not_authorized'){
Facebook.message(Facebook.authorize_message);
} else {
FB.login();
}
});

How can I customize Rails 3 validation error json output?

By default calling rails.model.to_json
Will display something like this:
{"name":["can't be blank"],"email":["can't be blank"],"phone":["can't be blank"]}
Instead of message i need to generate some status code that could be used on service client:
[{"field": "name", "code": "blank"}, {"field": "email", "code": "blank"}]
This approach is very similar to github api v3 errors - http://developer.github.com/v3/
How can I achieve this with Rails?
On your model you can modify the way as json operates. For instance let us assume you have a ActiveRecord model Contact. You can override as_json to modify the rendering behavior.
def Contact < ActiveRecord::Base
def as_json
hash = super
hash.collect {|key, value|
{"field" => key, "code" => determine_code_from(value)}
}
end
end
Of course, you could also generate the json in a separate method on Contact or even in the controller. You would just have to alter your render method slightly.
render #contact.as_my_custom_json
In your controller, when you render the output, in your case JSON content, add the following :
render :json => #yourobject, :status => 422 # or whatever status you want.
Hope this helps

Resources