facebook puts access code in web page - ruby-on-rails

Trying to write a facebook app and am attempting authentication. This is a rails 3.1 application
The First redirect where I give facebook my api and it returns a code seems to run well. But when I try to take that code and 'exchange it' from a access_token I run into problems.
The access token appears where the iframe aws.
Here is my code.
def index
#Load facebook.yml info
config = YAML::load(File.open("#{Rails.root}/config/facebook.yml"));
# setup client
redirect_to "https://graph.facebook.com/oauth/authorize?client_id=#{config['development']['app_id']}&redirect_uri=#{CALLBACK_URI}"
end
def callback
logger.debug("c")
config = YAML::load(File.open("#{Rails.root}/config/facebook.yml"));
if (!params.key?('access_token'))
logger.debug("A")
redirect_to "https://graph.facebook.com/oauth/access_token?client_id=# {config['development']['app_id']}&redirect_uri=#{CALLBACK_URI}&client_secret=#{config['development']['client_secret']}&code=#{params['code']}"
return
end
logger.debug("B")
access_token = params['access_token']
#me = FbGraph::User.me(ACCESS_TOKEN)
#name=#me.name
end
end

You are not supposed to redirect the user’s browser to the second endpoint (think for a moment, you are putting your app secret into that URL, so it would be easy for everyone to spot it there) – you are supposed to make a server-side call to that endpoint.
The docs clearly say so, https://developers.facebook.com/docs/authentication/server-side/:
“Once the user has authorized your app, you should make a server side request to exchange the code returned above for a user access token.”

Related

Cannot log out of iOS Swift app when connecting to Rails API using Doorkeeper & Devise

Ok - after 48 hours I give up. I have my Rails 3.2 app setup with Doorkeeper and Devise. The doorkeeper.rb initializer is pretty straight forward:
resource_owner_authenticator do
current_account || warden.authenticate!(:scope => :account)
end
and I am using the awesome p2_oauth POD in an iOS Swift app for OAuth2 connecting. Here's the problem/oddity:
When I go to login and the OAuth2 dance begins, p2 uses iOS's embedded web view to hit my Rails API. Since there is no current_account, it redirects appropriately to login. Great.
HOWEVER - I CANNOT LOG OUT.
I've tried everything:
warden.logout
signout
session.delete("warden.user.account.key")
clearing cookies in the Rails App when the logout route is called
clearing the sessions in the Rails App when the logout route is called
etc
Regardless of what I do, when I go to login again the Rails app is RETAINING the current_account!? I inspected the session and, sure enough, there is a warden.user.account.key still in the session! It's infuriating.
If I stop and restart the iOS app in the simulator, all is well again until I login/logout. So clearly it's like there is some session that iOS is maintaining and not clearing that the Rails app is reading. Im also making sure that my request has a no cache policy:
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.HTTPMethod = "DELETE"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.URLCache = nil
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler:
I honestly cant believe that no one else has come across this. How am I suppose to log out with this setup?
just wanted to follow up on your comment.
Just to recap what problem I had that brought me to your question:
User can log in fine but couldn't properly log out. Once they clicked log in again, it would automatically log in without asking for password again.
As you said, using the password grant flow would indeed avoid the cookie session. For me, I had to use a webview of some sorts in my iOS app. So I had to stick with the authorization_code grant.
I spent an entire week trying to figure out what was going on with doorkeeper+devise (all my links are purple...), and I finally found a solution that works for me. I'm not sure it's the proper way to do it though because I'm not familiar with ruby on rails at all. I'll have to go through the code again later.
Basically, what to do is add this to config/routes.rb where you say user_doorkeeper
use_doorkeeper do
controllers applications: 'doorkeeper', authorizations: 'authorizations'
end
Then add an authorizations controller here app/controllers/authorizations_controller.rb
class AuthorizationsController < Doorkeeper::AuthorizationsController
def new
super
env['warden'].logout
end
end
This forces the user to logout on log in. For the code, I think what it's doing is overriding/adding code to the original AuthorizationsController in Doorkeeper.
I found this solution on what looks like someone's blog, thank you good sir! This will do for now.
http://www.kluks.de/blog/posts/14-rails-4-single-sign-out-of-all-applications-with-doorkeeper.
------------ Edit ------------
For reference, for the provider side, I had been following this guide, while adding stuff I needed. I wouldn't recommend following her clientside tutorial though. That didn't work out for me at all.
doorkeeper.rb
Doorkeeper.configure do
use_refresh_token
force_ssl_in_redirect_uri false
#for authorization_code grant
resource_owner_authenticator do
current_user || begin
session[:user_return_to] = request.fullpath
redirect_to new_user_session_url
end
end
end
We had a similar issue, same setup on the backend, but we were able to resolve by clearing the cookies on the iOS app.
[[NSHTTPCookieStorage sharedHTTPCookieStorage] removeCookiesSinceDate:[NSDate dateWithTimeIntervalSince1970:0]];
Gabriel's answer worked only partially for me. The problem with it was, that the user is signed out directly after calling AuthorizationsController#new, so the subsequent AuthorizationsController#create request, which is required when the user authorizes the app for the first time, does not work.
In order to get the Authorization flow to work correctly, I needed to only sign out the user after the create action, or after the authorize#new if there is no need to authorize the app.
class AuthorizationsController < Doorkeeper::AuthorizationsController
def create
redirect_or_render authorize_response
# Sign out the user user clicked "Authorize" in the Authorize application page
warden.logout
end
private
def render_success
if skip_authorization? || matching_token?
redirect_or_render authorize_response
# sign out directly if there is no need for authorizing the app
warden.logout
elsif Doorkeeper.configuration.api_only
render json: pre_auth
else
render :new
end
end
end
Be aware that this case was used with a Mobile App as client and will not work if the intention is to have SSO for multiple apps.
I use doorkeeper 5.1 and devise 4.7, so it may look a little different for other versions.

Rails + Facebook: How to renew Facebook access token?

In a Rails app, we have implemented Facebook API. We have a Facebook page, where we need to automatically post statuses when a user will post something to the Rails app. The problem is, that we need automatically renew Facebook access token.
I am trying to do it this way, but as I am doing it the first time, I am a little bit lost here:
def fb_autopost
require 'net/http'
temp_token = 'my_short_time_token'
app_id = '145634995501123' # I didn't put here real token from obvious reason
app_secret = '0dd5fcf93b4280fb19bf6b80f487c123' # I didn't put here real token from obvious reason
puts "https://graph.facebook.com/oauth/access_token?client_id=#{app_id}&client_secret=#{app_secret}&grant_type=fb_exchange_token&fb_exchange_token=#{temp_token}"
puts '---'
url = "https://graph.facebook.com/oauth/access_token?client_id=#{app_id}&client_secret=#{app_secret}&grant_type=fb_exchange_token&fb_exchange_token=#{temp_token}"
response = open(url)
puts response.inspect
puts '---'
end
When I run this code, I get this error:
Processing by ListingsController#post_to_fb as HTML
Completed 500 Internal Server Error in 1015ms
OpenURI::HTTPError (400 Bad Request):
app/controllers/listings_controller.rb:194:in `fb_autopost'
I would like to ask you for help how to renew the token. Is in this case the best way to store the token in the database and like one day before expiring to run this action and renew the token?
Or is there any better way to do that?
Thank you so much
I personally use this gem : https://github.com/nov/fb_graph
You should find everything you need in the documentation (https://github.com/nov/fb_graph#normal-oauth2-flow)

Authorize to a rest api to consume it via rails

I try to use a server (hibiscus) via a rest api from my rails code. The hibiscus api has, as far as I see, no login method or similar. When I make a call:
class Hibiscus < ActiveResource::Base
self.site = "https://10.0.0.128:8080/webadmin/rest/hibiscus/umsaetze/query/Telekom"
end
test = Hibiscus.find(1)
I only get:
ActiveResource::UnauthorizedAccess: Failed. Response code = 401. Response message = Unauthorized.
Since hibiscus is a online banking app, there has to be some kind of login. In my browser I can open the according links, as soon as I logged into the system.
How can I make an authorization like the browser does in rails?
Or is there an even more elegant solution?
If some one will have this again in the future: You only have to provide Basic Auth with you login credentials you have to enter, when you login to the browser.
In Postman it looks like this

youtube oauth works from localhost, but not from production

The website I'm making makes it possible to connect your account with your youtube account. From localhost, this works perfectly, but from the site, which is step1tuts.appspot.com, it doesn't work. When I redirect the user to the authentication page from my website, I get the following message:
The page you have requested cannot be displayed. Another site was
requesting access to your Google Account, but sent a malformed
request. Please contact the site that you were trying to use when you
received this message to inform them of the error.
The code that handles this authentication looks like this:
client = youtube.get_client()
client.developer_key = 'AI39si759T7YcZ4E3XvICpZr3cGwQ0Ev4AjwyJrVSS6AW6NUc7_t10DX1JsngWzU4YoGjpsjAUTejav0hgXp9vDuM7a83tDXzQ'
client.client_id = 'step1tuts.com'
domain = 'http://' + os.environ['HTTP_HOST']+"/user/youtube_token"
scope = 'http://gdata.youtube.com'
url = client.GenerateAuthSubURL(domain,scope,secure=False,session=True)
self.redirect(str(url))
return
The url I'm redirecting to, ending in /auth_token then processes the token it gets back from youtube, but the error happens here.
Just for clarity, the youtube.get_client method is one that I developed to reuse the process of making the client appengine ready: the code for that is:
def get_client():
client = gdata.youtube.service.YouTubeService()
run_on_appengine(client)
client.developer_key = 'AI39si759T7YcZ4E3XvICpZr3cGwQ0Ev4AjwyJrVSS6AW6NUc7_t10DX1JsngWzU4YoGjpsjAUTejav0hgXp9vDuM7a83tDXzQ'
client.client_id = 'step1tuts.com'
user = users.get_current_user()
if(user and user.yt_token):
client.SetAuthSubToken(user.yt_token)
return client
While pasting in this code, I noticed that I'm duplicating the part where I give my developer key. I don't think that that's the problem, but I'll remove that from the authentication part of my code, and see what happens.
The problem must be tracable by watching the url that the user is redirected to, so just for some extra info, the url that I'm redirected to when I'm using the app on my local machine using the SDK, with which it works:
http://www.youtube.com/auth_sub_request?scope=http%3A%2F%2Fgdata.youtube.com&session=1&next=http%3A%2F%2Flocalhost%3A8081%2Fuser%2Fyoutube_token%3Fauth_sub_scopes%3Dhttp%253A%252F%252Fgdata.youtube.com&secure=0&hd=default
And the url that I'm redirected to when I use the same code on production:
http://www.youtube.com/auth_sub_request?scope=http%3A%2F%2Fgdata.youtube.com&session=1&next=http%3A%2F%2Fstep1tuts.appspot.com%2Fuser%2Fyoutube_token%3Fauth_sub_scopes%3Dhttp%253A%252F%252Fgdata.youtube.com&secure=0&hd=default
Interesting. All other urls work except this one. Probably a bug on youtube side? I just added a '.' at the end of your domain and the request seems to go through. Maybe you can try that?
http://www.youtube.com/auth_sub_request?scope=http%3A%2F%2Fgdata.youtube.com&session=1&next=http%3A%2F%2Fstep1tuts.appspot.com.%2Fuser%2Fyoutube_token%3Fauth_sub_scopes%3Dhttp%253A%252F%252Fgdata.youtube.com&secure=0&hd=default

Private channels with Pusherapp (using Rails)

I just got through the hello world for Pusherapp. Now I want to create private channels so users only read messages that they are supposed to read.
The Pusher docs only give some details on how to do this, and I'm kind of lost.
From the docs:
...
The Pusher JS library is returned
a socket_id when it connects to
Pusher.
When it attempts to subscribe to a
private channel, it sends back an AJAX
request to your server with the
channel_name and socket_id as
parameters.
The default URL for this is
http://yourserver.com/pusher/auth.
...
class PusherController < ApplicationController
def auth
if current_user
response = Pusher[params[:channel_name]].authenticate(params[:socket_id])
render :json => response
else
render :text => "Not authorized", :status => '403'
end
end
end
Given a unique user id (current_user.id), how can I authenticate that user then have him/her subscribe to the corresponding channel?
Thanks
This blog post on the implementation seems to explain things a bit more: https://pusher.com/docs/client_api_guide/client_private_channels
The authorization scheme is based on
the idea that, rather than
implementing custom user
authentication, and adding complexity
and state to pusher, we should trust
the existing level of authentication
offered by your application. We also
wanted to ensure that someone reading
data sent from your application to the
browser would not be able to connect
to a channel as that user, and
therefore couldn't include any secrets
in the page HTML.
Sounds like your application's business logic should authenticate the user and decide that they should access the private channel.
Their diagram shows:
Once authenticated, the app requests to subscribe the user. Pusher replies with the socket_id. Then they are connected using that.
Here's how they describe it:
As shown in this diagram, a unique
socket id is generated and sent to the
browser by Pusher. This is sent to
your application (1) via an AJAX
request which authorizes the user to
access the channel against your
existing authentication system. If
successful your application returns an
authorization string to the browser
signed with you Pusher secret. This is
sent to Pusher over the WebSocket,
which completes the authorization (2)
if the authorization string matches.
The example at the bottom of the blog post further clarifies:
Suppose you have a channel called project-3, to which users A and B have access, but not C. You'd like to make this channel private so that user C cannot listen in on the private events. Simply send events to private-project-3 and subscribe to it in the browser. As long as you're using the latest javascript (version 1.3 or above), you'll see that a POST request is made to your application to /pusher/auth. This will currently fail, and therefore the subscribe request will not be made to the socket.
So, to me this sounds like:
1) Request to subscribe is sent to Pusher
2) Pusher POSTs to your /auth method to determine if the user can access the channel
3) If your business logic allows the user to access this channel, the auth method returns the "ok" response:
auth = Pusher[params[:channel_name]].socket_auth(params[:socket_id])
content_type 'application/json'
return JSON.generate({
:auth => auth
})
I haven't used Pusher itself, but its model seems to mirror the structure of other push-based models. Hope this helps!

Resources