rails authenticate_or_request_with_http_basic - ruby-on-rails

in my RoR application i need to protect a page with basic authentication and i want that the credentials are asked every time that a user link to that page.
so i added a filter before the operation, like this:
before_filter :request_confirm, :only => [:delete_device]
and the filter method is:
def request_confirm
user = User.find_by_id(session[:user_id])
authenticate_or_request_with_http_basic do |nick, pass|
nick == user.nickname and pass == user.password
end
end
it's ok, but only the first time because rails save inserted data, so the following times the filter will be execute but the credential won't ask.
I don't know where credential are saved.
.

This is how method authenticate_or_request_with_http_basic and in general how HTTP authentication works. authenticate_or_request_with_http_basic can be reworded as: "First try to authenticate and if not authenticated, request for authentication". The source code of this method is as follows:
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
end
So what happens. When you first hit the URL that invokes this action, this authenticate_or_request_with_http_basic returns HTTP response 401 Unauthorized. The browser understands this is a request for authentication and shows you a dialog to enter username and password, and then resends the request for the same URL but includes your credentials into request headers. You filter is hit again, and this time method authenticate_or_request_with_http_basic sees that there are authentication headers in the request and authorises you successfully. And the browser will send these auth headers on each following request to this domain (until you close the browser).
So if you need just test it several times you can close and reopen browser. I believe using only these methods it is impossible to ask for authentication and authenticate on every request because when the application gets request from browser with Auth headers it can not tell whether this is request immediately after authentication request, or these are headers preserved before.
But this can be somehow accomplished using cookies or value stored in session.

Related

Authenticating docusign via Rails API (Omniauth + Devise) + JS Frontend

I'm trying to create an authentication flow using Auth Code Grant where I've added necessary omniauth strategy for Docusign to create /auth/docusign routes in Rails API only application.
Here are the steps followed
I'm issuing a request to the route from VueJS client.
window.open("http://localhost:4000/auth/docusign", "targetWindow", "width=350,height=250")
After user enters credentials and on successful login I'm calling the callback:
class SessionsController < Devise::SessionsController
def docusign
internal_destroy
#success = false
userinfo = request.env['omniauth.auth']
request_info = request.env['omniauth.params']
if userinfo
info = userinfo.info
cred = userinfo.credentials
user = User.find_by(email: info['email']) || User.find_by(id: session[:user_id])
if user
organization = user.organization
organization.organization_providers.where(provider_name: 'Docusign').destroy_all
OrganizationProvider.create(email: info['email'], token_expires_at: Time.at(cred['expires_at']), token_expires_at: Time.now, provider_name: 'Docusign', organization_id: organization.id, token: cred.token)
#success = true
end
end
render 'sessions/docusign'
end
end
I'd like to pass some params (which I'm accessing in the callback as request.env['omniauth.params']) for executing some backend tasks in the method.
When I try window.open("http://localhost:4000/auth/docusign?email='"+email+"'", "targetWindow", "width=350,height=250")
It says that the url doesn't match with any redirect urls
I have also tried passing in redirect_to('/auth/docusign', query: query) but on doing so, it doesn't open in a browser due to CORS.
I'm also trying to set it in session cookie, but since it's an API only server, I'm still working towards setting up cookie store.
Question
Which is the best way to achieve this? To pass some params in the callback and retrieve it.
Then the execution flow continues on the Rails server and the window serves a page with an appropriate response as per authentication status. However during this time, the client window which started the request is not aware of the authentication outcome.
Question
How can I communicate to the VueJS client that the authentication process is completed?
Question
Am I doing the above flow correctly or are there any better ways to achieve the same?
Thanks in advance
You need to log into your DocuSign Developer Account, Click on Admin and go on the left nav down to "API and Keys" where you can find the integration key you set. Did you set one?
If you did, you should find it and then add the redirectUri to the OAuth settings for that key (client ID in OAuth).
That is why DocuSign login tells you that the redirectURI doesn't match. You can add http://localhost:4000/auth to the list and that should work for your local env.
You cannot past custom variables on the redirectUri, it has to match exactly to the one you entered. If you need to pass values to it, there's a way to do that using state.
Here is how the URL should look, notice the &state= part of it:
https://account-d.docusign.com/oauth/auth?
response_type=code
&scope=YOUR_REQUESTED_SCOPES
&client_id=YOUR_INTEGRATION_KEY
&state=YOUR_CUSTOM_STATE
&redirect_uri=YOUR_REDIRECT_URI
&login_hint=YOUR_LOGIN_HINT
You can put whatever you want in there (URI encoded of course) and that value would come back to you when redirected back also with &state= parameter.
This solves the problem and allows you to pass arguments back to your redirect URI.

HTTP Token: Access denied

i m getting message "**HTTP Token: Access denied**" when access via browser http://localhost:3000/api/v1/tasks.json?auth_token=szVkqLnUbdzbekV8B-n_
but when i access from terminal that's working on success curl http://localhost:3000/api/v1/moments.json -H 'Authorization: Token token="szVkqLnUbdzbekV8B-n_"'
here code
class Api::V1::TaskController < ApplicationController
before_action :autentifikasi
def index
#tasks = current_user.tasks
end
private
def autentifikasi
authenticate_or_request_with_http_token('Premium') do |token, options|
#current_user = User.find_by(authentication_token: token)
end
end
end
end
anybody help me please !! what's wrong with my code ?
There is nothing wrong with you code - the error is in your testing methodology.
The cURL example properly sends a Authorization: Token header and sends the token along as well.
Requesting http://localhost:3000/api/v1/tasks.json?auth_token=szVkqLnUbdzbekV8B-n_ in a browser would simply set params['auth_token'] as it is a query parameter. Which will of course cause the authentication to fail.
Rails and most sane frameworks do not treat HTTP headers and query parameters as equivalent. That would leave your app looking like swiss cheese.
If you want to test token based auth via a browser you should use a plugin such as Postman which allows you to setup the request headers. Better yet is to write an actual automated integration test.
A Guide to Testing Rails Applications
RSpec Rails: Request spec
Postman
It is because authenticate_or_request_with_http_token expects an Authorization: Token from in a request header.
You are setting the header in the cURL command while in browser you are passing it as a query parameter.
So there is no token in the Request Header so your method is unable to find the token when accessed via a browser.

Where is the Session Stored in Rails?

In Rails, I have implemented the below code for user auth (confirmed to be correct). However, I wanted to confirm my thinking for this strange session[:session_token]. is this the "cookie" that is stored in the browser?
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user, :signed_in?
private
def current_user
#current_user ||= User.find_by_session_token(session[:session_token])
end
def signed_in?
!!current_user
end
def sign_in(user)
#current_user = user
session[:session_token] = user.reset_token!
end
def sign_out
current_user.try(:reset_token!)
session[:session_token] = nil
end
def require_signed_in!
redirect_to new_session_url unless signed_in?
end
end
My understanding so far of how this works is that whenever the browser/client sends a request to rails, the cookie (with the session[:session_token]) is also sent over, thus allowing the current_user method to find the user. Is my understanding correct? This is strange to me because there's a gap of knowledge of how exactly the browser/client gets access to the session cookie when we declare it in ApplicationController (Rails-side).
You are pretty much there. Although, I have a feeling you might be confusing apples with oranges...
Sessions:
Very often in dynamic web sites one would want to store user data between HTTP requests (because http is stateless and you can't otherwise associate a request to any other request), but you don't want that data to be readable and/or editable on the client-side inside of the URL (like.. yourwebsite.com/yourPage?cookie=12345&id=678), and so on..., because you don't want the client to play around with that data without passing through your server-side code.
One way to solve this problem is to store that data server-side, give it a "session_token"(as you called it), and let the client only know (and pass back at every http request) that token. This is how the session is implemented.
Cookies:
The most common technique for implementing sessions in Rails involve using cookies, which are small pieces of text placed on the user’s browser. Because cookies persist from one page to the next, they can store information (such as a session_token or whatever else you want) that can be used by the application to retrieve the logged-in user from the database.
Where is the Session Stored in Rails?
Using both of the above concepts I can now tell you that the default session store inside of Rails is CookieStore, which is about 4KB in size.
To put it simply...
def sign_in(user)
#current_user = user
session[:session_token] = user.reset_token!
end
...method that you defined places the user into a temporary session.
Then the idea is that the following...
def current_user
#current_user ||= User.find_by_session_token(session[:session_token])
end
...method would find and retrieve the user from the database corresponding to the session token and initialize it to a variable you specified.
Additional info:
You should also note that there is an important difference between Rails's session and cookies helper methods...
They both generate cookies, however, session[...] method generates temporary cookies, which should expire upon the browser exit, and cookies[...] method creates persistent cookies, which do not.
Additionally, I would suggest having a look at Section 2 of Ruby on Rails Security guide. You might find it useful.
Hope this helps you out.
Session is stored in server side. And,
Cookie is stored in client side (in browser cookie). And,
When client/browser send a request to rails server, every time cookies are sent to rails server.
When a session is set in rails server, like: session[:user_id] = 4,
Rails store it in server side.
Session is saved in server side like key value pair (like json object)
For each browser, Rails set a session identifier in cookie, so that, Rails can find the correct session information for a request.
Without session identifier in cookie, Rails do not know, what session belongs to what browser.
So, session will not work without cookie.
Edit: Explain: sessions are stored server side
Suppose, I am using your web application, and after login I will be redirected to home page.
I open login page, input username and password, and click login button.
The form is submitted to sessions#login action.
in sessions#login - you check username and password - and set session[:session_token]:
if username and password is correct
random_unique_identifier_string = #user.remember_token
session[:session_token] = random_unique_identifier_string
redirect_to root_url
end
When server run this code session[:session_token], server need an unique identifier for each browser session.
So, server generate an unique identifier for this browser, such as: abc123
Server set all session variables in a place (may be in some folder or in database), label this folder as abc123.
Now server send a cookie request to browser - to set cookie _ebook_session = abc123.
(I see, if my app name is ebook, in rails, cookie name is like: _ebook_session)
Now the page redirect to home page.
** Note: Everything above happen in single request **
Now, in my browser, I want to open some page that need authentication (suppose, dashboard page).
You added before_action: require_signed_in! in dashboard controller.
So, when I open dashboard page in my browser, browser by default send all cookies with every request. so _ebook_session cookie is sent to server. Your server gets the value of _ebook_session cookie is abc123. Now your application know we need to look in abc123 folder for session. Now you can get value of session[:session_token] from abc123 folder.
** I have explained second request above **
Each browser needs unique session identifier.
Important: _ebook_session cookie will be set in browser in first request. If we already have _ebook_session cookie set in a browser, we do not need to set it again, second, third and next requests in that specific browser.
I hope, you understand.

Capture The Original URL After A Redirect?

I am using Rails 3 and Devise for authentication.
I want to track whenever a user enters my site from another domain or by typing in the URL. Assume the following page on my site:
http://www.mysite.com/somepage
If a visitor requests this URL by clicking a link on another site or types it in to his browser, I want to put the URL into a cookie for later use. If /somepage does not require login it works fine. In a before_filter I just check to see if the referrer is not from mysite.com.
However, when /somepage requires a login, Devise makes a redirect, which results in a second request to my login page. The original referrer is carried forward to the new request. So my code thinks it's the original requested URL and overwrites the cookie. Wrong.
I'm probably just having a mental block, but I can't see how to determine that the page that is redirected to is not original page requested.
You could probably get some ideas from this article.
It seems like you should filter out anything that includes the user path, since you have no problem getting the referrer if the page doesn't require login.
So for example you could use a regex to filter out users/sign_in, users/sign_out, etc.
request.referrer = request.fullpath unless request.fullpath =~ /\/users/
or
request.env['HTTP_REFERER'] = request.fullpath unless request.fullpath =~ /\/users/
You would need to put this in some kind of before_filter above the authenticate before_filter so it is called first.

Rails: Accessing the username/password used for HTTP Basic Auth?

I'm building a basic API where user information can be retrieved after that user's login and password are correctly sent.
Right now I'm using something like this:
http://foo:bar#example.com/api/user.xml
So, what I need to do is access the user/password sent in the request (the foo and bar) but am not sure how to access that info in a Rails controller.
Then I'd check those variables via a quick User.find and then set those as the username and password variables for authenticate_or_request_with_http_basic.
It's possible I'm looking at this at the completely wrong way, but that's where I'm at right now. :)
The answer to your question of how to get the credentials from the request is this:
user, pass = ActionController::HttpAuthentication::Basic::user_name_and_password(request)
However authenticate_or_request_with_http_basic is all you need to do basic auth:
class BlahController < ApplicationController
before_filter :authenticate
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
# you probably want to guard against a wrong username, and encrypt the
# password but this is the idea.
User.find_by_name(username).password == password
end
end
end
authenticate_or_request_with_http_basic will return a 401 status if credentials are not supplied, which will pop up the username/password dialog in a browser. If details are given then those are passed to the block provided. If the block returns true the request goes through. Otherwise the request processing is aborted and a 403 status is returned to the client.
You can also check out Railscast 82 (thats were the code above is from):
http://railscasts.com/episodes/82-http-basic-authentication
The rails plugin Authlogic supports this functionality (as well as much more) out of the box. You could root around in the source for it, or simply integrate it into your existing application.
Edit:
After digging around the source code for Authlogic, I found this file which uses the following piece of code to grab the username and password:
def authenticate_with_http_basic(&block)
#auth = Rack::Auth::Basic::Request.new(controller.request.env)
if #auth.provided? and #auth.basic?
block.call(*#auth.credentials)
else
false
end
end
I'd look a bit further into where it all goes, but I've got to get to bed. Hope I was of some help.

Resources