Authentication between applications using OAuth - ruby-on-rails-3

I have two rails application running at different ports. First at 3000 and the second at 4000. Both of them use Devise gem for auth.
First application plays the role of OAuth provider and the second on OAuth consumer.
I've followed this and that tutorials to build my environment.
Almost all works fine. I've successfully generated key and secret for consumer application. And successfully authorize at provider application.
There are two methods at my client application:
def auth
#consumer = OAuth::Consumer.new 'KEY', 'SECRET', :site => "http://localhost:3000"
#request_token = #consumer.get_request_token
session[:request_token] = #request_token
redirect_to #request_token.authorize_url
end
def auth_callback
#request_token ||= session[:request_token]
#access_token = #request_token.get_access_token :oauth_verifier => params[:oauth_verifier]
#request = #access_token.get '/user_info.json'
render :text => #request.body.inspect
end
And API method at provider application:
class UsersController < InheritedResources::Base
before_filter :login_or_oauth_required
load_and_authorize_resource
def info
logger.info current_user.present? # => false
#info = { } # here I've collect user info for current_user
respond_to do |format|
format.json { render :json => #info }
end
end
end
Shit happens when I try getting user info at line: #request = #access_token.get '/user_info.json'
When I call it in consumer application user already unauthorized at provider application.
How I can stay authorized at provider's resource?
upd: I've got current_user.present? # => false in case I pass authorization for info action (before_filter :login_or_oauth_required, :except => [:info]) otherwise I've got redirected to login page.

You don't stay authorized in the provider.
On every request to your API, you'll receive the access token (either in parameters or header), and from this token you'll be able to determine who is the current_user. There is no session among requests.
This gem may help if you need an OAuth provider.

The load_and_authorize_resource will deny access to info action.
just add :except attribute
load_and_authorize_resource :except => [:info]

Related

Ruby on Rails security regarding session cookies

In my app, I have a User model and it has a rememberable_token column. When creating a user, a random secure string is saved in a before_create filter to act as a secure token for the user:
user.rememberable_token = SecureRandom.urlsafe_base64
In the session controller, it creates a permanent cookie with the value of that token so that the user doesn't get logged out when closing the browser and only gets logged out when they log out via the logout action:
Session controller:
def create
.
.
cookies.permanent.signed[:permanent_user_session] = user.rememberable_token
end
def logout
cookies.delete :permanent_user_session
redirect_to root_url
end
The cookie is used in the application controller to determine if there is a current user as well as in a before_filter that is used in a few controllers to determine if a user is logged in and authorized.
Application controller:
def current_user
#current_user ||= User.find_by_rememberable_token(cookies.signed[:permanent_user_session]) if cookies.signed[:permanent_user_session]
end
def authorize
unless User.find_by_rememberable_token(cookies.signed[:permanent_user_session])
render :action => 'login'
end
end
The question is if this is safe or if it is prone to session hijacking? If it is prone to hijacking, would it be alright if in the session#logout method it created a new rememberable_token for the user just before deleting the existing cookie (but not creating a new cookie with that value)?
Thank you.
If someone is stealing the cookie, this code:
def current_user
#current_user ||= User.find_by_rememberable_token(cookies.signed[:permanent_user_session]) if cookies.signed[:permanent_user_session]
end
will still work. On your logout method you have to delete the token from user table and recreated at login.
Basically, what you are doing at create should be done at each login and reverted at each logout.
I'd probably do this:
On session create:
random_string = SecureRandom.urlsafe_base64
cookies.permanent.signed[:permanent_user_session] = random_string
user.rememberable_token = Digest::MD5.hexdigest(random_string) && user.save
On session destroy:
cookies.delete :permanent_user_session
In the application controller:
def current_user
#current_user ||= User.find_by_rememberable_token(Digest::MD5.hexdigest(cookies.signed[:permanent_user_session])) if cookies.signed[:permanent_user_session]
end
def authorize
unless #current_user
render :action => 'login'
end
end
This way, you're storing a hash of the token and a new one is regenerated for every new login (and expired on every log out). Rails takes care of CSRF but long-term sessions are probably not a good idea.

From Rails devise auth to backbone & api?

i want to rebuild an app which is a typical rails 3.2 mvc app into a API + Frontend (Backbone) only. As I have no experience in building APIs in rails including authenticatin:
What's the best way to authenticate with devise using backbone? Using auth_tokens?
How should I make he API? Just printing out JSON or use a gem like Grape?
thanks in advance!
I can explain you the way i do this :
First, i install a standard rails application with devise. After that, i create my own session controller :
class SessionsController < ApplicationController
def authenticate
# this method logs you in and returns you a single_access_token token for authentication.
#user = User.find_for_authentication(:email => params[:user][:email])
if #user && #user.valid_password?(params[:user][:password])
render :json => {:user => {:email => #user.email, :id => #user.id, :firsname => #user.firstname, :lastname => #user.lastname, :team_id => #user.team_id, :singleAccessToken => #user.generate_access_token}}
else
render :json => {:errors => ["Nom d'utilisateur ou mot de passe invalide"]}, :status => 401
end
end
end
As you can see, i send a request to this url with the json looking like :
{
user => {
email => "myemail#toto.com",
password => "monpass"
}
}
And my controller return me the json with user data if every thing is fine, or an error. On json with user, i return an access_token used on next requests to check that the user is allowed to request. I made this filters in my application controller :
class ApplicationController < ActionController::Base
protect_from_forgery
protected
def user_access_token
request.headers["HTTP_X_USER_ACCESS_TOKEN"] || request.headers["HTTP_USER_ACCESS_TOKEN"]
end
def current_user
if token = user_access_token
#user ||= User.find_by_access_token(token)
end
end
def require_user
unless current_user
render :json => {:error => "Invalid Access Token"}, :status => 401
end
end
def require_owner
unless current_user && current_user == object.user
render :json => {:error => "Unauthorized"}
end
end
end
As you can see, on each next request, i will add the access_token in html header on key : HTTP_USER_ACCESS_TOKEN
So, i can check if the user is allowed to make the request.
To make an API, you can use the Rails API gem as see here :
http://railscasts.com/episodes/348-the-rails-api-gem
Good luck.

How to allow a user to enter a password when deleting an authorization in devise/omniauth

I have a rais 3 app that uses devise and omniauth to allow users to register/login via their twitter account and/or with local login credentials. Everything works fine for registering and logging in. My problem occurs when a user chooses to destroy their twitter authorization without first establishing a local password. If a user destroys their authorizations, then I would like to route them to new_password_path so that they can choose a password for future log-ins.
Here is the controller code:
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :except => [:create, :failure]
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication #existing user is logging-in with existing authentication service
flash[:notice] = "Signed in successfully."
set_home_location_cookies(authentication.user, authentication.user.home_lat, authentication.user.home_lng)
sign_in(:user, authentication.user)
redirect_to root_path
elsif current_user #existing user who is already logged-in is creating a new authentication service for future use
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'], :token => omniauth['credentials']['token'])
current_user.update_posting_preferences(omniauth['provider'])
flash[:notice] = "Successfully linked to your #{omniauth['provider'].titleize} account."
redirect_to root_path
else #new user is creating a new authentication service and logging in
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in(:user, user)
redirect_to root_path
else
session[:omniauth] = omniauth.except('extra')
session[:user_message] = {:success => false, :message => "userSaveError"}
redirect_to new_user_registration_url
end
end
end
def failure
flash[:alert] = "Could not authorize you from your social service."
redirect_to root_path
end
def destroy
#authentication = current_user.authentications.find(params[:id])
current_user.update_posting_preferences(#authentication.provider)
#authentication.destroy
flash[:notice] = "You have successfully destroyed your link to your #{#authentication.provider.titleize} account."
if current_user.authentications.empty? && current_user.encrypted_password.empty?
sign_out
flash[:alert] = "Alert: Your account does not currently have a password for account authorization. You are in danger of losing your account unless you create a new password by using this form."
redirect_to new_password_path(current_user) and return
else
redirect_back_or(root_path)
end
end
The code results in a "could not find valid mapping for nil" error triggered by my redirect_to new_password_path(current_user) and return command
I would greatly appreciate some help figuring out this problem.
Thanks!
OK. I'll admit it. I implemented the authentications controller from a tutorial without studying devise routing to learn what was going on behind the scenes. Last night I reviewed the docs and figured out my problem. What is funny is that the above routine did work on an older version of devise but does not work on devise 1.5.3.
In the destroy action I sign-out the current_user then I try to route to the new_password_path sending in "current_user" as a parameter. Not surprisingly, at that point "current_user" has been nulled out. So, I get the, "could not find a valid mapping for nil" error. Here is my easy fix:
def destroy
#authentication = current_user.authentications.find(params[:id])
user = current_user
current_user.update_posting_preferences(#authentication.provider)
#authentication.destroy
flash[:notice] = "You have successfully destroyed your link to your #{#authentication.provider.titleize} account."
if current_user.authentications.empty? && current_user.encrypted_password.empty?
sign_out
flash[:alert] = "Alert: Your account does not currently have a password for account authorization. You are in danger of losing your account unless you create a new password by using this form."
redirect_to new_password_path(user) and return
else
redirect_back_or(root_path)
end
end

devise+omniauth devise helper like current_user,user_signed_in? not working

I am using devise and create login with Facebook using omniauth, but having problem of lost the devise helper methods access like current_user and user_signed_in? methods are not working.
EDIT
AuthenticationController
def create
omniauth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(omniauth["provider"], omniauth["uid"]) || User.create_with_omniauth(omniauth)
session[:user_id] = user.id
redirect_to dashboard_path(user.id), :notice => "Signed in!"
end
redirect_to USercontroller dashboard method
UserController
before_filter :logged_in
def dashboard
#user = User.find(params[:id])
#comment = Comment.new
#comments = #user.comments.all.paginate(:page => params[:page], :per_page => 5)
end
so here control should go to dashboard method after checking logged_in method in ApplicationController
logged_in method in ApplicationController
Application Controller
def logged_in
if user_signed_in?
return true
else
redirect_to root_path
flash[:message] = "please login"
end
end
when I logged in using facebook following code generated at console
Started GET "/users/52/dashboard" for 127.0.0.1 at Thu Mar 29 12:51:55 +0530 2012
Processing by UsersController#dashboard as HTML
Parameters: {"id"=>"52"}
Redirected to http://localhost:3000/
Filter chain halted as :logged_in rendered or redirected
Completed 302 Found in 2ms (ActiveRecord: 0.0ms)
in the above code control is render from logged_in method to root_path but it shold render dashboard_path
So I am guessing User_signed_in? helper is not working I also use current_user in stead of that generate same error
As I see, user_signed_in? is working, but returns false, as for Devise user is not logged in. To fix this, just replace the session id storing with Devise sign_in method in your controller action:
def create
omniauth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(omniauth["provider"], omniauth["uid"]) || User.create_with_omniauth(omniauth)
sign_in(:user, user)
# actually if you really really need that id in the session, you can leave this line too :)
session[:user_id] = user.id
redirect_to dashboard_path(user.id), :notice => "Signed in!"
end
After creating the user account via Facebook, how do you sign in the user?
You should still be using devise helpers like sign_in_and_redirect. Something like:
user = User.build_from_omniauth(omniauth)
if user.save
sign_in_and_redirect(:user, user)
end
Then you should be able to use helpers like current_user and user_signed_in? (which just check if current_user is not nil).
Taking a look at your edit, my answer is still valid. What you need to do is use sign_in_and_redirect(:user, user) instead of just setting the id in the session.
You can easily customize where the user is redirected after sign in with devise.
Another thing, remove this logged_in filter, Devise has a authenticate_user! method that you can use as a before_filter. It will redirect the user to the sign in page, and when they login, it will redirect them to the page they were trying to access.
You're using Devise, so try to take advantage of that, and go read the doc ;)

How to configure ActiveAdmin routes to get 2 signin pages (for a user and for an admin) with 1 User model?

My project had 2 models: AdminUser and User.
I'd removed the AdminUser model (and db tables associated) and now I'm using my User model with an admin? method (I've had a boolean admin field in the users table)
ActiveAdmin documentation:
You can skip the Devise user class all together by using the
skip-users flag:
$> rails generate active_admin:install --skip-users
NOTE: If you don’t use the default user settings, you will need to
configure the settings in config/intializers/active_admin.rb to suite
your needs.
Here's what I've found in the initializer:
config.authentication_method = :authenticate_admin_user!
config.current_user_method = :current_admin_user
So, I've modified application_controller.rb as:
def authenticate_admin_user!
render(:file => "#{Rails.root}/public/403.html", :status => 403, :layout => false) and return if user_signed_in? && !current_user.admin?
authenticate_user!
end
def current_admin_user
return nil if user_signed_in? && !current_user.admin?
current_user
end
And in routes.rb:
devise_for :admin_users, ActiveAdmin::Devise.config.merge(:class_name => 'User')
How can I configure ActiveAdmin to access admin section with the ActiveAdmin default path: /admin?
My objective is to have 1 User model, but 2 separate signin pages:
/users/sign_in (default devise signin)
/admin (ActiveAdmin signin)
For now, when I try to access /admin, I'm redirected to /users/sign_in page :-(
Thx for your advices...
Since you skip generating new user model, replace any admin_user string in the generated stuff from ActiveAdmin to user. So instead of :authenticate_admin_user! replace it with :authenticate_user! and so on in migration files: instead of admin_user_id put user_id same for admin_user_type ... etc!