Sessions & User Authentication in Padrino - ruby

I'm very new to Padrino (I come from a PHP background), and ruby web frameworks in general, and have been trying to figure out how to implement a simple user authentication and session management system, but have not really found much documentation on the subject. I know that padrino comes with a pre-built "Admin" package that includes user login/authentication, ect, however I'd rather roll my own from scratch, rather than trying to customize their solution to fit my own needs.
So my question is, in Padrino how would I go about implementing a simple session-based authentication system for logging in users by setting session data once a user/pass combo has been validated against the database, retrieving that session data to check if the user is logged in when a request is made to protect certain pages/resources, use the session data to get the user's ID/role/ect, and then destroy that session when user logs out. As a PHP programmer I'm used to using the $_SESSION superglobal for this purpose, is there something akin to this in padrino/ruby? I noticed there is a enable :sessions in app.rb, is :sessions the pardrino equivalent?

Yup,
session[:cart] = cart_id
Cart.find(session[:cart].to_i) if session[:cart].present?
For authentication purposes you can avoid padrino-admin and builtin auth using a more more simple way:
# in app.rb
use Rack::Auth::Basic, 'Restricted Area' do |username, password|
user == 'admin' and password == 'pwd'
end
If you need to control a bit more your sessions/cookies you can use:
set :sessions,
:key => '__awesome_key',
:secret => 'awesome_password',
:expire_after => 1.year

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].

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.

RubyCAS with a persistent single ticket & Sinatra

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 .

Ruby On Rails authentication with Devise and (warden) callbacks

I have a rails app that is using Devise, with a User model, no scope.
I've also added activeadmin gem in to the app, Active Admin is a gem used for adding an admin dashboard in your application. It uses Devise for logging in users and creates a separate admin_user model for the admins.
I am allowing anonymous, non-logged in users to create shopping carts, creating a session[:cart_id]. If a user logs in I want associate the user with the cart, something like
Cart.find(session[:cart_id]).user = current_user
I was planning to use Wardens callbacks wardens callbacks to impliment this, something like so :
Warden::Manager.after_set_user :scope => :user do |user, auth, opts|
Cart.find(session[:cart_id]).user = user
end
However I get an error if I do that:
<% unless user_signed_in? %> throws an error :admin_user user is not logged in
Anyone got any ideas what is going on?
I've looked at related questions, but no help:
How to access session from Warden/Devise after_authentication callback in Rails
Where should warden callbacks be placed in a rails app?
The AdminUser model that Active Admin uses also executes this callback. So, maybe, an if can solve your problem:
Warden::Manager.after_set_user :scope => :user do |user, auth, opts|
Cart.find(session[:cart_id]).user = user if user.class == User
end
Actually it turned out the issue was solved by setting the default scope in warden,in the devise initializer file.
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
# config.default_scope = :user
Since the active admin routes were added above the devise routes for my user, the adminuser became the default user.

How to use basic authentication with httparty in a Rails app?

The command line version of 'httparty' with basic authentication works simple and great:
httparty -u username:password http://example.com/api/url
But now I'm looking for the way I can add the basic auth to a HTTParty.get call from within a Rails app. First of all, for testing purposes, I want to hard code the login credentials in the Controller. Just to make sure it works. But I can't find any documentation or examples how you can pass these along.
A HTTParty.get without credentials works fine:
#blah = HTTParty.get("http://twitter.com/statuses/public_timeline.json")
But I don't see how I can make a variation on this that accepts the -u username:password part.
The next challenge for me (am very new to Ruby/Rails) is to get the user credentials from a user form and pass it along dynamically, but most important for me now it to get the hard coded version to work.
auth = {:username => "test", :password => "test"}
#blah = HTTParty.get("http://twitter.com/statuses/public_timeline.json",
:basic_auth => auth)
Two points,
If you are hitting Twitter's api, unless I'm mistaken I don't think they allow basic auth anymore :( So you may want to look into something like OmniAuth for OAuth sign-in. You don't need HTTParty or a sign-in form for this, you link to the Twitter sign-in and the user enters credentials there, then Twitter sends a callback request to your app once authenticated. OmniAuth does most of the work for you, you just pull the info you need out of what it gives you in the callback route.
But even so, you will still need the OAuth 'consumer key' and 'consumer secret' which are specific to your application (how Twitter authorizes your application, as distinguished from the user). And you don't want these, nor any auth keys, in your source code.
A typical way of doing this is stick them into a config/omniauth.yml file which is not checked in to source control:
twitter:
key: CONSUMER_KEY
secret: CONSUMER_SECRET
And then load them in an initializer config/initializers/omniauth.rb :
consumers = YAML.load("#{Rails.root}/config/omniauth.yml")
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, consumers['twitter']['key'], consumers['twitter']['secret']
end
You could take a similar approach with loading basic auth username/passwords, just stick them in some object that you'll have access to from wherever you make the HTTParty calls.

Resources