Rails authentication and request variable not working as expected - ruby-on-rails

I am developing a rails application, that allows optional HTTP basic authentication.
Authorization should be allowed, but not mandatory.
To do this, I am trying to use a before_action inside the application controller, that will try to find a user matching the given credentials and either write that user or nil into a global variable.
I tried this, but the block for authenticate_with_http_basic doesn't seem to be called at all (The console doesn't show the username and password, that I supplied, however logging outside of the block works):
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate
def authenticate
authenticate_with_http_basic do |username, password|
logger.info "Login:"+username+" "+password
#auth_user = User.authenticate(username, password)
end
end
end
And I tried this:
def authenticate
if user = authenticate_with_http_basic { |username, password| User.authenticate(username, password) }
#auth_user = user
end
end
I also tried this, which throws an error undefined method 'split' for nil:NilClass. When looking at the documentation, I see that split is being called on part of the request. Am I doing something wrong with just assuming the request variable should be accessible from a before_action inside the application controller?
def authenticate
username, password = ActionController::HttpAuthentication::Basic::user_name_and_password(request);
logger.info "Login:"+username+" "+password
#auth_user = User.authenticate(username, password)
end
I just need a simple function that gives me username and password as string variables. What am I doing wrong? Is there another way to accomplish that seemingly simple functionality?
Update
The things I tried seem to work. My only mistake was to use a regular webbrowser to debug my API. Most web browsers don't send authorization to the server, before they get a www-authenticate header back, even if the user explicitly included it in the URL.
As long as it is just used as an API or accessed through other ways, this should not be a limitation. However, this kind of optional authorization, that does not present an authorization dialog doesn't work with regular browsers (at least not as a HTTP authorization). It is not a problem with Rails, just the way browsers are built.

you might just be using the wrong method. this is one of the examples from ApiDock:
class AdminController < ApplicationController
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic('Administration') do |username, password|
username == 'admin' && password == 'password'
end
end
end
see this question for more details: In Ruby on Rails, what does authenticate_with_http_basic do?
UPDATE
i don't see any problems without requesting basic auth. it works as expected:
class HomeController < ApplicationController
before_action :authenticate
private
def authenticate
authenticate_with_http_basic do |username, password|
logger.info "try basic-auth without requesting it: username=#{username} password=#{password}"
end
end
end
calling an action with credentials:
curl -I "http://uschi:muschi#hamburg.onruby.dev:5000/"
gives the following logs:
[hamburg.onruby.dev] [127.0.0.1] [044cb7ea-56a9-4f] Started HEAD "/" for 127.0.0.1 at 2013-10-21 17:40:54 +0200
[hamburg.onruby.dev] [127.0.0.1] [044cb7ea-56a9-4f] Processing by HomeController#index as */*
[hamburg.onruby.dev] [127.0.0.1] [044cb7ea-56a9-4f] try basic-auth without requesting it: username=uschi password=muschi

Related

Rails 7 pass session params to pundit policy

I'm using Devise to authenticate User in my Rails 7 app and Pundit for authorization. I would like to extend the standard login flow to check if the user has met the 2FA requirements. 2FA is simple and delivered by an external microservice - the user at each login enters the SMS (text message) code he got on his phone.
In a nutshell new login flow should be:
user authentication via Devise
allow to authorize if session[:_2fa] == 'success'
redirect to provide_code_path if session[:_2fa] == 'failed'
Because the user must go through the 2FA process every time he logs in, I've store such info inside the session params as session[:_2fa]. Now to make all the authorization and redirect dance I want to have access to that session to allow or not. I'm aware of this topic but it's 7y old so maybe today there is some modern approach instead of creating fake model just to get access to the session?
Code below:
# application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!, :authorized_user
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def authorized_user
authorize :global, :_2fa_passed?
end
private
def user_not_authorized
flash[:alert] = t('errors.user_not_authorized')
redirect_to provide_code_path
end
end
# global_policy.rb
class GlobalPolicy < ApplicationPolicy
def _2fa_passed?
session[:_2fa] == 'success'
end
end

Issues with Rails API mode with Clearance gem?

I have followed the steps here https://github.com/thoughtbot/clearance/wiki/API-Authentication (inserted below) to get my Rails API only app going with authentication.
I have run into a couple issues. The first being that "cookies" is undefined.So I commented that out.
Now I am getting
NameError (undefined local variable or method 'form_authenticity_token' for #<BookmakersController:0x00007ffa6f370c78>):
app/controllers/application_controller.rb:12:in `authenticate_via_token'
I can't seem to resolve this last one. BookmakersController is one of my controllers obviously where I have before_action :authenticate_via_token
I am using Postman with Authorization headers set to send a get request to my app.
Any ideas how I can get through this error?
class ApplicationController
protected
def authenticate_via_token
return unless api_token
user = User.find_by_api_token(api_token)
sign_in user if user
cookies.delete(:remember_token) # so non-browser clients don't act like browsers and persist sessions in cookies
end
private
def api_token
pattern = /^Bearer /
header = request.env["HTTP_AUTHORIZATION"]
header.gsub(pattern, '') if header && header.match(pattern)
end
end
class MyController < ApplicationController
before_action :authenticate_via_token
end

How can I get current_user by directly visiting an API url in rails app? I am using ng-auth-token and devise_token_auth for authentication

I am using ng-auth-token and devise_token_auth for authentication which is working fine. I am able to login from front end but when i visit an API url directly in browser it doesnt show any current_user. What i want to do is i want to integrate paypal checkout, so when i come back from paypal to my app after user authorization, current_user is nil and also session variable is empty (even if i set some session variable before going to paypal site).
If i add
before_action :authenticate_user!
it gives me
Filter chain halted as :authenticate_user! rendered or redirected
even if i am logged in.
I don't know how can i handle these callback response from other apps.
I found a workaround to this, but still waiting for a proper solution.
# In ApplicationController
def authenticate_current_user
head :unauthorized if get_current_user.nil?
end
def get_current_user
return nil unless cookies[:auth_headers]
auth_headers = JSON.parse cookies[:auth_headers]
expiration_datetime = DateTime.strptime(auth_headers["expiry"], "%s")
current_user = User.find_by(uid: auth_headers["uid"])
if current_user &&
current_user.tokens.has_key?(auth_headers["client"]) &&
expiration_datetime > DateTime.now
#current_user = current_user
end
#current_user
end
and use this in controllers
# In any controllers
before_action :authenticate_current_user
source: https://github.com/lynndylanhurley/devise_token_auth/issues/74
Thanks.
Addon to Ankit's solution (rep too low to comment):
This was failing for me on post requests because Rails was stripping out the cookies due to protect_from_forgery being set:
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
include Pundit
protect_from_forgery with: :null_session # <-- this was the culprit
end
Removing protect_from_forgery entirely "solved" the issue, though I'm not happy with it.
The real issue (on my end, at least) is that ng-token-auth is supposed to be including the token in the header, but is only found in the cookies. My current guess is that either 1) ng-token-auth isn't properly setting its HttpInterceptor, or 2) some other interceptor is messing with it after the fact. (I've seen the ng-file-upload can cause issues, but I'm not using that...)
I have ended up with this code in ApplicationController:
before_action :merge_auth_headers
def merge_auth_headers
if auth_headers = cookies[:auth_headers]
request.headers.merge!(JSON.parse(auth_headers))
end
end

Does Devise cach authentication data?

I ran into this weird situation here. I developed a JavaScript-based frontend for my Rails backend API.
I have a login form that posts username and password to my TokensController that returns the authentication_token stored in the database. This token is being stored in a cookie and submitted with every form.
Now I wanted to implement a logout function. So, I delete the cookie so no token gets submitted in a request. (Or a wrong one, in this case the header is Authentication: Token token="undefined")
But still, the Rails backend returns 200 OK with all the data, although the wrong token is defined. How is this possible? Is there any other session cache that is used to authenticate a request?
This is my super class that implements the authentication:
module Api
class SecureBaseController < ApplicationController
prepend_before_filter :get_auth_token
before_filter :authenticate_user!
protected
def get_auth_token
puts token_value
params[:auth_token] = token_value
end
def token_value
if header && header =~ /^Token token="(.+)"$/
$~[1]
end
end
def header
request.headers["Authorization"]
end
end
end
Even puts prints "undefined" in the console, as submitted by the web application, yet it authenticates the user correctly?

Rails authenticate_or_request_with_http_basic

When a user tries to connect via this method and it fails. How do I redirect_to? Thanks in Advance.
class ApplicationController < ActionController::Base
protect_from_forgery
USER_NAME, PASSWORD = "admin", "admin"
helper_method :authenticate
private
def authenticate
begin
authenticate_or_request_with_http_basic do |user_name, password|
user_name == USER_NAME && password == PASSWORD
end
rescue
"HTTP Basic: Access denied"
else
redirect_to root_path
end
end
end
Rails - authenticate_or_request_with_http_basic custom "access denied" message
In the past, we've made authenticate a before_filter which will just perform the authenticate_or_request_with_http_basic, and then have our redirects/renders happen in their respective actions.
I just re-read your question & code sample. What exactly are you looking to have happen? A redirect on fail of the authentication?
Edit: I've never done this, but it seems like trying to follow the docs might be your best bet, in particular, the "more advanced basic example."
I ultimately don't know enough about the inner workings of basic auth, but it strikes me as it could also boil down to an Apache/(insert your awesome web server here) configuration. Someone will definitely correct me on that if I'm wrong.

Resources