RubyCAS with a persistent single ticket & Sinatra - ruby

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 .

Related

Get user from device/warden session cookie

I need to deserialize the session cookie by hand to extract the user from it. The enviroment is rails, though it is not in the http server context, so there is no request for devise to automatically deserialise. There is the Warden::SessionSerializer, though I did not really get how to use it.
Before I wasn't using Devise so I used this method to deserialize the rails session cookie:
def decrypt_cookie(cookie)
cookie = CGI.unescape(cookie)
config = Rails.application.config
encrypted_cookie_salt = config.action_dispatch.encrypted_cookie_salt
encrypted_signed_cookie_salt = config.action_dispatch.encrypted_signed_cookie_salt
key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
secret = key_generator.generate_key(encrypted_cookie_salt)
sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
encryptor.decrypt_and_verify(cookie)
end
Since some people might be skeptical whether there is a context in which manually deserializing the cookie can ever be proper:
The client (browser) authenticates via HTTP, and the session is of course stored in the cookie. Rails is also running (rails runner) the websocket server that responds to certain privileged actions. For these I need to (re-)authenticate the user. If someone sees a different (better) way of doing that, I'd be glad to read it. :)
The way I describe for deserializing the cookie is just the right way. It is actually the same in Devise, the keys just changed and I jumped to quickly to the conclusion that there must be a different serilization process going on.
Do you have to use Devise? I posted previously on using Warden to roll your own token auth which you have complete control over. If you decide like me to just do the easy thing and implement auth yourself you will see its easy to lookup a user from a token via your models.
You can access the session object in your controller. What key do you use to store the user_id? You should just be able to do session[:key], so if you store the id as user_id, then session[:user_id].

Persist login data from subdomain Ruby on Rails

On my app every user logs from subdomain.myapp.com, but i have a special case which is more complex because the log is from www.myapp.com and then I need to redirect the user to subdomain.myapp.com/somePath
The problem is that obviously the session data is discarded by the browser because the login was made over 'www.' and not over 'subdomain.'. I need to persist this data when redirection is done.
How can i handle this situation? I only need this flow in the case i mentioned, not in whole app
I use devise gem for authentication.
In Rails for every request from browser a method subdomains is provided to collect all subdomains in an array.
Returns all the subdomains as an array, so ["dev", "www"] would be
returned for "dev.www.rubyonrails.org".
You can probably collect all the subdomains in application controller as request variable is provided there and use them accordingly in other controllers.
Subdomains in Rails 3 will be a good Railcast to refer though.

Authentication for Sinatra REST API app

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.

Cookie is changed on every request, using sinatra and Rack::Session::EncryptedCookie

I'm new to web app development & have just started learning Sinatra framework. I'm currently developing a login system.
Here's the necessary code :
require 'sinatra'
require 'openssl'
require 'encrypted_cookie'
configure do
use Rack::Session::EncryptedCookie, :secret => "foo-bar-baz", :httponly => true
Then, I'm storing username in the session if he/she is authorized :
post '/' do
if authorize(params[:name], params[:password], params[:csrfkey])
session[:name] = params[:name]
end
end
The cookie is encrypted all right. And it is also remembering session values.
But while using Firebug I noticed that at every request(page load, refresh, etc) the cookie's content is changed & I mean completely changed. It seems like Sinatra is sending new cookie at every request.
Previously, I was using enable :sessions & the cookie never changed.
So my questions are, is it normal to have cookie's content changed at every request while in a session?
Is it happening because it is encrypted?
I searched high & low on the net but no one has got this problem I guess..
The source for encrypted_cookie shows that it generates different encrypted output every time it is called regardless of the input. There are 2 reasons for this:
The library would have to know what the session value was during the last request. It doesn't, all it does is accept a single input, the given session. If you wished to circumvent this and just rewrite the cookie (I suppose) you could, since you have the extra information available higher up in the Sinatra app.
It's more secure. It doesn't leak information (if the cookie doesn't change then an observer of the cookie knows nothing changed during the request), and it gives an attacker less time to try and get to a meaningful value.

OmniAuth on Heroku with custom domain results in Application Error

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.

Resources