I have an application on Heroku which uses omniauth and authenticates correctly when I visit myapplication.heroku.com/auth/open_id, input my google endpoint, and get redirected back.
However, when I visit myapplication.com, with heroku custom domains setup and working for every other url, I get Application Error from heroku after being redirected back from Google (I have not tried other openid providers).
I have hoptoad setup and it is not sending me any notifications about the specific error (probably because omniauth is middleware). Nothing shows up in heroku logs besides that there was a [nginx] GET request at the url which gave the error.
it probably doesn't matter, but this is a rails app.
localhost production testing works fine.
ideas?
I am not sure whether this fixes your problem, but I encountered a similar problem on my app (OAuth with Facebook, Rails, Heroku). It turned out the problem was caused by the following line:
session["devise.facebook_data"] = env["omniauth.auth"]
(which stores the OAuth data in the session in case the user does not have an account yet and has to complete a signup form before he can be persisted).
This caused a ActionDispatch::Cookies::CookieOverflow (which also was not reported by Hoptoad/Airbrake) for some users whose omniauth.auth hash was too large to be stored in the session cookie. Hence I fixed this issue by preprocessing the hash and throwing out everything that is not needed, before saving it to session. Maybe your bug is related to this?
I had the same problem.
myurl.com resulted in 502 bad gateway, while .herokuapp.com worked fine.
I had set
use Rack::Session::Cookie
To enable session cookies, but for some reason, the Ngnix proxy at Heroku didn't like this. When i changed it to:
use Rack::Session::Cookie, :key => 'rack.session',
:path => '/',
:expire_after => 14400,
:secret => 'change_me'
ie. made sure there was no domain key in the hash.
Related
I'm trying to create a script to access the Quizlet API
Those API are protected with OAuth2 and I'm using this oauth2 ruby gem https://github.com/intridea/oauth2
The gem's GitHub page shows an example but for me doesn't work and I feel I'm missing some pieces.
This is part of the example:
client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth2/callback')
# => "https://example.org/oauth/authorization?response_type=code&client_id=client_id&redirect_uri=http://localhost:8080/oauth2/callback"
token = client.auth_code.get_token('authorization_code_value', :redirect_uri => 'http://localhost:8080/oauth2/callback', :headers => {'Authorization' => 'Basic some_password'})
Two quetions:
What should I do with the result of authorize_url? This url should be opened on a browser to proceed with the login, but the gem doesn't open it automatically. Should I ask the user to do so?
What about the callback URL? The example uses http://localhost:8080/oauth2/callback but is not clear if the gem itelf is in charge of listening on that port.
Any advices?
It might be easier to look at what the Quizlet API is asking you to do, and skipping the integration with the oauth gem. I could be wrong and I'd be interested to see how the the gem is used, but it's still a good idea to look at how to implement an oauth client step-by-step.
This is taken from the Quizlet API docs:
Send the user to https://quizlet.com/authorize. This can be done by displaying a link for the user to click, or redirecting server-side.
Once the user accepts/denies your oauth (this happens on the quizlet website, not your own), quizlet will send a request to your server. Because of the /authorize call, Quizlet will now have a redirect_uri which they will use to get in touch with your server. In your server's action for this route, you can get the authorization_code.
Send the authorization_code to https://api.quizlet.com/oauth/token, and in the response get an access_token whic you use for the rest of the api requests.
About the gem
You should send the authorize url to users. This is step 1 above
the gem is not in charge of listening for the redirect. You need to use a web server for this.
I'm using linkedin-oauth2 gem
to connect with LinkedIn Api. The following snippet gives me 403 error:
client = LinkedIn::API.new(user_access_token)
client.profile # works well
client.network_updates # gives 403 error
I gave all possible permissions to the app (including rw_nus). Any ideas?
You are getting probably this issue because you are not setting properly the scope for the omniauth gem to request this permissions on the client-side oauth2 besides how the app permissions are configured in the Linkedin developer network site.
Try addind something like
:scope => 'r_fullprofile r_contactinfo r_emailaddress r_network'
to the existing config line you might have in the Omniauth (or Devise) initializer file.
provider :linkedin, ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET']
Then you should get both Apps working good.
Check the permission because linkedin api is changed go through the url for more info http://www.oodlestechnologies.com/blogs/recent-changes-in-linkedin-api
I think it's a problem on LinkedIn side connected with cache. Adding permissions to the existing app doesn't work but creating app with needed permissions gives exactly what i want.
I'm building an API with Sinatra (using Angular for the client side and want others to have access to API) and have it also be an OAuth provider. I am wondering what the best route to take (work off existing gems or roll own solution off Warden or something).
Have used devise and doorkeeper for authentication and oauth before with Rails, wondering what best solution for Sinatra is.
Ideally I don't want the views or be able to extend/mod the actions of an existing solution, as I'm interacting with it purely as an API.
I just recently did the same thing using the following answer from S/O
What is a very simple authentication scheme for Sinatra/Rack
It implies a user model, but instead of using that, I just set a user and admin password in my config file. Then I had a login form that just took a password. When the user enters that password, I checked it against the one in settings and set the session['user'] to :admin or :user according to whichever it matched (or nil if none). Then on each of my routes, I called auth: :user or auth: :admin accordingly.
APIs normally accept your login request and send you an authentication token which you need to pass back in each call. This is very similar to cookie based sessions where your browser automatically passes back the cookie which is acquired on initial website visit.
From what I've seen in Sinatra's docs, you could make a session-based authentication system like this:
enable :session
disable :show_exceptions
use Rack::Session::Pool,
key: 'session_id'
post '/login' do
user = User.login_success(params)
halt 401 if user.nil?
session[:user] = user
200
end
get '/fun' do
user = session[:user]
halt 401 if user.nil?
halt 403 if !user.has_permission_for '/fun'
"fun was had"
end
Now all you need to do in your client is to pass back the cookie token returned in response to initial visit when requesting an API function. This can be done with any web client library that supports cookie stores (such as libcurl) or by inserting the session cookie into the request header manually. Rack::Minitest functionality also supports cookies, so you can test your API with minitest.
See Sinatra API Authentication.
Quick summary:
Sinatra has no built-in auth.
It's best to build auth yourself (see the link).
There are gems available, but you probably won't need them for something as simple as an API.
Alright, I'm a huge RubyCAS noob, and this is driving me crazy.
I have installed the rubycas-client gem, and have followed along with the official Sinatra setup at this repo. The way this setup is done, every time I request a page, I receive a one-time ticket that's appended to the URL as a query like so:
http://localhost:9393/?ticket=ST-1373928850... etc.
If I refresh the page, I get a Sinatra error saying the ticket has already been used up!
I've two questions, then.
Is the ticket-per-reload standard behavior?
How do I save my CAS login for a session and still retain single sign-out?
What I've done:
I have gone and tried to implement :sessions in Sinatra, but this causes single-sign-out to fail.
I have gone and done my best to follow the steps in the rubycas-client GitHub Repo (replacing ActiveRecord session storage with Sinatra's :session helper).
The RubyCAS documentation for Sinatra is fairly poor, so I'm looking for a definitive answer to this.
It is doing the correct thing when you try and reload the page with the same ticket. That ticket has already been validated. When you get the validation response you need to then set your own applications cookie or other session option.
I usually add a method that will add a session attribute to the user's cookie like:
session["cas"]["username"] = <user from cas validation response>
Then in future requests the Sinatra application can protect whatever routes you want with a helper method like:
cas = RestClient::Resource.new "#{cas_url}/login", :timeout => 5
checked = cas.get
return true if checked.code == 200
In my configure block for Sinatra I do this:
use Rack::Session::Cookie, :key => "example.com",:secret => "veryrandomhex"
I hope this helps, have any questions let me know.
UPDATE BELOW
While discussing this problem, we've uncovered that RubyCas says to not use a regular cookie session for your ruby application in production, while using CAS. What you'll want to do is:
A. Make sure your cookie expires at the same time or sooner than the CAS cookie
And/Or
B. Make sure your cookie is per browser session, then revalidate the CAS user on next browser session.
For Rack cookie you would specify this extra config for when the cookie is set to expire: :expire_after => 1440, (where 1440 is in minutes)
In case of the ruby CAS there are two kinds of session :
(1). The application session.
(2). The Single sign on (SSO) session.
you can use sinatra-session gem for managing the application session and just use session_end! helper method to destroy the application session. For destroying the SSO session unset the session[:cas_ticket] parameter in log out route.
example:
In case of the Sinatra:
get '/logout' do
session_end! # provided by sinatra-session gem
session[:cas_ticket] = nil # session variable set by CAS server
end
here we are explicitly setting the session[:cas_ticket] to nil, however you can use session.clear in logout route to destroy the current session data .
The issue here is with multiple instances of the same Sinatra (Rack) app deployed on Passenger+Apache on different sub URIs with HTTP basic auth to keep away unwanted access:
I have 4 instances of a Sinatra app deployed on my domain as:
example.com/private/foo
example.com/private/moo
...
...
Access to all of them is protected by HTTP basic authentication using the Rack::Auth::Basic middleware. config.ru for all of them look like:
# ...
users = {'user' => 'password'}
use Rack::Auth::Basic, 'realm' do |username, password|
users.key?(username) && users[username] == password
end
run MyApp
The only thing the changes from one config.ru to another is the 'realm' parameter.
Now the issue is that once I have logged into one of the apps, say private/foo, Chrome doesn't prompt me for a username and password for other apps (private/moo etc.). This is counterintuitive since all instances are uniquiely identified by their URLs. Using different credentials for each instance does work, but shouldn't Chrome request credentials at least once for each instance? One thing I noticed is that the first time I log into one of the instances, Chrome says 'The server at example.com:80 requires a username and password'. I would have expected 'The resource example.com/private/foo requires a username and password'. Isn't that how it is supposed to work?
I checked Rack::Auth::Basic source code and Wikipedia's article on HTTP Basic Auth and came up with nothing to help my case :(.
In basic authentication, the realm parameter isn't send back to the server. So the server can't really check if the client is sending authorization header for the same realm or not. It depends on the client. Rack's implementation of HTTP basic authentication is correct. So:
Now the issue is that once I have logged into one of the apps, say private/foo, Chrome doesn't prompt me for a username and password for other apps (private/moo etc.). This is counterintuitive since all instances are uniquiely identified by their URLs.
As Andrew pointed out and is clear from the RFC, URL doesn't play a role there. But if '/foo' is protected, '/foo/moo' is protected under the same realm.
Using different credentials for each instance does work, but shouldn't Chrome request credentials at least once for each instance?
Under the scenes what is happening (on inspecting with debugger tools) is that, after I have logged once into one of the apps, say private/foo, Chrome re-sends the same authorization header to other apps, say private/moo, without being challenged first.
The RFC says that the client may send the corresponding authorization header for a realm without being challenged by the server first.
Looks like Chrome is either treating all my apps to be under the same realm or re-sending the same authorization header across different realms. I don't think that is the expected behavior but I could be missing something. Firefox behaves same. Anyway, that wasn't the essence of the question.
The theme of the question was "How do I get Chrome to request me username and password at least once for each instance? Basic auth isn't working the way I expected it to; why?"
Use Digest authentication (RFC 2617 again). Rack implements the MD5 algorithm version under Rack::Auth::Digest::MD5. Set different opaque for each instance and you are good to go:
# ...
realm = "Description of the protected area."
opaque = "Secret key that uniquely identifies a realm."
users = {'user' => 'password'}
use Rack::Auth::Digest::MD5, realm, opaque do |username|
users[username]
end
opaque is sent back by the client and can be verified on the server side that the authorization request is for the correct resource. Job of realm seems descriptory in nature -- which area or resource are you trying to protect? what id do I flash?
RFC: https://www.rfc-editor.org/rfc/rfc2617