Rails HTTP Basic check for authenticated? - ruby-on-rails

I've been googling this one and haven't turned up anything useful:
Assuming you use http basic auth in Rails is there a simple method to check if the user is authenticated? Ie. a way you can do something like this in a view:
- if http_basic_authenticated?
# show admin menu

Try this:
class ApplicationController < ..
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic do |username, password|
#authenticated = username == "foo" && password == "bar"
end
end
def authenticated?
#authenticated
end
helper_method :authenticated?
end
You can now use authenticated in your view.
Please write tests!

Use a session parameter accessible through a method defined in your ApplicationController.
class ApplicationController < BaseController
...
def authorize
session[:authorized] = true
end
def http_basic_authenticated?
session[:authorized]
end
def end_session
session[:authorized] = nil
end
end
P.S. I'm not a security expert, so I can't comment on the suitability of using this in a production environment.

From the oficial code you can extract snipets to use something like this in any controller inherited by ApplicationController:
class ApplicationController < BaseController
...
protected # Only inherited controllers can call authorized?
def authorized?
request.authorization.present? && (request.authorization.split(' ', 2).first == 'Basic')
end
...
end

well, as I could know, there's no way to tell a view that the request is not authenticated, you could just tell the view that it is authenticated, but why?
let's see the process of a request:
Client Request
Controller
View
and in the 2nd step, the particular controller's method, which is before-filtered by the authentication method, that is, if you can go to the 3rd step -- the view, the request must be authenticated.

Related

Reseting session based on user agent and/or ip change. Rails with Devise

I would like to reset session whenever user agent and/or user ip changes. Project uses Rails with Devise for authentication, CanCanCan for authorisation.
My approach is:
class ApplicationController < ActionController::Base
before_action :authorize_ip
def authorize_ip
if warden.authenticated?
warden.session['ip'] ||= request.ip
warden.session['user_agent'] ||= request.user_agent
warden.logout if warden.session['ip'] != 'request.ip' &&
warden.session['user_agent'] != request.user_agent
end
end
end
From my understanding, it should set warden.session['ip'] and user_agent once and then for following requests whenever request['ip'] or user_agent changes, session should be dropped and User should be logged out. However, when tested with different browsers, warden.session['user_agent'] changes according to what browser I use. I suppose I'm misunderstanding something. Also, if there is a better approach to this problem, please share.
Thanks!
I've solved the issue by adding this to initializers/devise.rb
Warden::Manager.after_authentication do |_user, auth, _opts|
auth.raw_session['warden.user.ip'] = auth.request.ip
auth.raw_session['warden.user.user_agent'] = auth.request.user_agent
end
and this to application controller:
class ApplicationController < ActionController::Base
before_action :authorize_ip_user_agent
protected
def authorize_ip_user_agent
return true unless session['warden.user.ip']
warden.logout if session['warden.user.ip'] != request.ip &&
session['warden.user.user_agent'] != request.user_agent
end
end

How to pass a query param to Devise's login page

I have a Devise customisation issue that I'm struggling with.
We have a query parameter included in some email links that need to be passed to the login URL if the user is not already authenticated when they click on the link. For example, if the email link is:
http://my.host.com/my/path/1234?x=y&foo=bar
I'd like unauthenticated users to be redirected to
http://my.host.com/login/?foo=bar
i.e., one specific query param needs to be passed – I don't care about any others on the login form, but if all the query params have to be passed I could live with that.
I've scoured SO, and the docs & source for both Devise and Warden, but can't find a straightforward way of doing this. Apologies if I'm missing something obvious.
In the end, we got the effect we wanted by going down a very slightly different route.
Rather than including the param in the query URL of the redirect to the login, we were able to modify the code concerned to accept a session parameter instead.
Then, in application_controller.rb:
class ApplicationController < ActionController::Base
before_filter :persist_login_param
...
def persist_login_param
session[:foo] = params[:foo]
end
end
And in the sessions controller, we act upon the value of the session parameter, discarding it once done.
Slightly messy, but it works and gets code pushed out to production now, which is the important thing!
Can use unless statement in before_action, then unauthentication users can see any devise controller actions
class ApplicationController < ActionController::Base
...
before_filter :sending_params
before_action :check_auth, unless: :devise_controller?
...
protected
def check_auth
unless user_signed_in?
redirect_to new_user_session_path(sending_params)
end
end
def sending_params
params.permit(:first_param, :second_param)
end
...
And can access to params without generation devise controllers by generate devise views and use in views
<%= params[:first_param] %>
If need send params to omniauth controller, you can change link in devise views
<%= link_to 'Sign in by provider', omniauth_authorize_path(:user, :provider, first_param: params['first_param']) %>
Where :user - name devise model, :provider - name omniauth provider (:facebook i.e.)
In omniauth controller you can use
def first_param
request.env['omniauth.params']['first_param'] ||= ''
end
For an unauthenticated user, in order to pass specific params foo=bar out of http://my.host.com/my/path/1234?x=y&foo=bar to your login, perform as below:
Assuming the above email link goes to action myaction then add the following code to myaction
def myaction
if member_signed_in?
redirect_to root_path
else
## If user is unauthenticated direct them to login page with specific params
redirect_to new_member_session_path(foo: params[:foo])
end
end
Above action will pass params foo to the devise login page and the actual link in browser address bar would look as below:
http://my.host.com/login?foo=bar ## Without / after login
You can access the param by overridding Devise::SessionsController. For Example :
class Users::SessionsController < Devise::SessionsController
...
def new
#foo = params[:foo]
super
end
...
end

Redirect to a specified URL after a POST in Rails

So often I have a form in some webpage that the user submits to a POST, PUT or DELETE action in Rails where I want it to redirect to a specified URL if the submission was a success. I typically make a hidden extra parameter called to with a path like /users. So if the form submission failed, it just stays on that form, but if it succeeds then the browser is redirected to /users.
I'd like to automatically look for this parameter and always redirect to it if a form submission succeeded in any controller/action. Do I put this in the ApplicationController within an after_action?
class ApplicationController < ActionController::Base
after_action :redirect_if_success
private
def redirect_if_success
redirect_to params[:to] if params[:to]
end
end
I guess I can check the request object if this was a POST, PUT or DELETE action. How do I know the submission was a success? Will a redirect_to in the after_action override any redirect_tos in the form controller?
I think the solution is define private method redirect_if_success in application controller but call it directly in the action. eg:
class ApplicationController < ActionController::Base
private
def redirect_if_success(default_ur)
redirect_to params[:to] || default_url
# or similar logic
end
end
class UserController < ApplicationController::Base
def create
redirect_if_success("/users") if #user.save
end
end
I would create a helper method
def redirect_to_location
redirect_to params[:to] && params[:to].present?
end
and I would use it explicitly in each action I want this behaviour.
However, you could experiment a bit. To keep this logic in after_action you would need to setup some state that would let you know whether you need to redirect or not.
You could do :
def save
if #user.save
#follow_redirect = true
end
end
and check #follow_redirect flag in after_action filter. Does not look like a very pretty solution but it would work.
You could also try to inspect response variable to see if you have already redirected or rendered an action: (not sure if it would work but it's fun to experiment)
So you could check:
if you need to redirect (action is post/put/delete) and params[:to] is present and
if you have not already redirected/redirected
# this is not a copy-paste code but rather to demonstrate an idea
class ApplicationController < ActionController::Base
after_action :redirect_to_location
protected
def is_redirectable?
%w{post put delete}.include?(request.method) && params[:to].present?
end
def already_redirected?
!response.status.nil? # not sure if it would work at all
end
def redirect_to_location
redirect_to params[:to] if is_redirectable? && !already_redirected?
end
end

Redirect to specific URL after logging in

Is there a way in Devise 1.0, the library for Rails 2.3, to redirect to a specific URL and not root_url after logging in?
EDIT: forgot to mention it's Devise 1.0
Chances are that your user is being redirected before after_sign_in_path is called. This happens if the user tries to go to a page that is protected by authentication directly. This will happen all the time if you have your root_path ('/') protected by authentication.
There's a discussion on google groups about this topic:
http://groups.google.com/group/plataformatec-devise/browse_thread/thread/454c7ea651601a6c/64f0adc3be8036d0?#64f0adc3be8036d0
The quick and dirty solution is to overwrite stored_location_for to always return nil like so:
class ApplicationController < ActionController::Base
...
private
def stored_location_for(resource_or_scope)
nil
end
def after_sign_in_path_for(resource_or_scope)
my_favorite_path
end
end
I think the after_sign_in_path_for method in Devise is what you're looking for.
Define that method in your ApplicationController and it will over-ride Devise's default implementation. This is what the documentation specifies to do.
Details here: http://rdoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers:after_sign_in_path_for
Suppose you want to show user's dashboard after logging in.
class HomesController < ApplicationController
def index
if current_user //returns nil if not logged in
#users = User.all
render :dashboard
end
end
def dashboard
#users = User.all
end
end
in routes.rb:
root :to => 'homes#index'
If logged in, if-block is entered in index action and dashboard.erb is rendered.
(make sure to initialize all variables, required by dashboard.erb, in your if-block)
Otherwise rails renders index.erb

Rails - ACL9 caching in session

I implemented authentication with Authlogic and authorization with Acl9. Now I'm trying to avoid multiple hits to database to check if user is admin by keeping this in the session.
What I thought is that this code should work:
class ApplicationController < ActionController::Base
...
helper_method :current_user_session, :current_user, :is_admin
...
private
def is_admin
return current_user_session[:is_admin] if defined?(current_user_session[:is_admin])
current_user_session[:is_admin] = current_user.has_role?(:admin)
end
So basically on a first call to is_admin helper method, it should add a boolean value to session[:is_admin] and then for any other calls, take it from the session. But I receive this error:
undefined method `[]=' for #<UserSession: {:unauthorized_record=>"<protected>"}>
And I stuck here. What am I doing wrong?
I had to use session[] instead of current_user_session[]. This code works as charm:
(ApplicationController)
helper_method :current_user_session, :current_user, :is_admin?
...
def is_admin?
return session[:is_admin] if !session[:is_admin].nil?
session[:is_admin] = current_user.has_role?(:admin)
end
(view template)
<% if is_admin? -%>
...
It will cache admin role in session on a first attempt and then will take it from there.

Resources