I'm getting this
OmniAuth::Strategies::OAuth2::CallbackError at /auth/google/callback
csrf_detected | CSRF detected
My code:
require 'sinatra'
require "sinatra/json"
require "sinatra/config_file"
require 'omniauth-oauth2'
require 'omniauth-google-oauth2'
use Rack::Logger
config_file "config/app_config.yml"
use Rack::Session::Cookie, secret: '5fb7w345y3489f523y4h'
configure do
enable :sessions
end
use OmniAuth::Builder do
provider :google_oauth2, settings.google[:client_id], settings.google[:secret],
{
:scope => "userinfo.profile",
:access_type => "offline",
:prompt => "select_account consent",
:name => "google"
}
end
get '/list' do
json get_list
end
get '/' do
%Q|<a href='/auth/google'>Sign in with Google</a>|
end
get '/auth/:name/callback' do
#auth = request.env['omniauth.auth']
#auth.inspect
end
My callback is returning both code and state.
This problem occurs with rails when the domain defined in /config/initializer/session_store.rb is different from the origin/redirect_uri defined in the google api console.
MyApp::Application.config.session_store :cookie_store, key: '_app_session', domain: 'my_app.com'
Removing the domain params or using the same domain on both sides fixed the problem.
If you are using Devise with OmniAuth you need to skip the extra omniauth.rb initializer file and simply add config.provider "KEY", "SECRET" inside your initializers/devise.rb and then carry on with your implementation.
Got the same problem
(google_oauth2) Callback phase initiated.
(google_oauth2)
Authentication failure! csrf_detected:
OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF
detected
Last Omniauth-oauth2 update introduced the "state" param has a mandatory field.
Some people suggest using provider_ignores_state: true but it's a bad idea because it introduces csrf flaw
Guess we'll have to downgrade to previous version to keep google_oauth2 working.
Issue it on https://github.com/intridea/omniauth-oauth2/issues/58
Are you hitting back and reattempting to log in? I was getting this issue and it was really confusing me, but it was because I was going back to retry. If I typed in the address again, I wouldn't get the issue
Related
I am using sidekiq in my rails application.
By Default, Sidekiq can be accessed by anybody by appending "/sidekiq" after the url.
I want to password protect / authenticate only the sidekiq part. How can i do that?
Put the following into your sidekiq initializer
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(user), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_USER"])) &
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_PASSWORD"]))
end
And in the routes file:
mount Sidekiq::Web => '/sidekiq'
Sorry to late to the party, but Sidekiq's wiki recommends the following for Devise:
To allow any authenticated User:
# config/routes.rb
authenticate :user do
mount Sidekiq::Web => '/sidekiq'
end
To restrict access to User.admin?
# config/routes.rb
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => '/sidekiq'
end
This wiki post also has many other security schemes.
This was tested using Rails 5.1.3, Devise 4.3 and Sidekiq 5.0
See "Security" under https://github.com/mperham/sidekiq/wiki/Monitoring
Sidekiq::Web uses Rack::Protection to protect your application against typical web attacks (such as CSRF, XSS, etc). Rack::Protection would invalidate your session and raise Forbidden error if it finds that your request doesn't satisfy security requirements. One of the possible situations is having your application working behind a reverse proxy and not passing important headers to it (X-Forwarded-For,X-Forwarded-Proto). Such situation and solution could be found in this article and issue #2560...
If you're using Devise (or other Warden-based authentication), you can do this, supposing you have an AdminUser model in your app.
# config/routes.rb
# This defines the authentication constraint
constraint = lambda do |request|
request.env['warden'].authenticate!({ scope: :admin_user })
end
# This mounts the route using the constraint.
# You could use any other path to make it less obvious
constraints constraint do
mount Sidekiq::Web => '/sidekiq'
end
If you are rolling your own custom authentication, then you can use the below example which is referenced in the docs here.
# lib/admin_constraint.rb
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
user = User.find request.session[:user_id]
user && user.admin?
end
end
# config/routes.rb
require 'sidekiq/web'
require 'admin_constraint'
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
The accepted answer is good, but I think that it can be implemented more securely, as Sidekiq documentation mentions (it got edited to demonstrate the right solution after I posted).
To protect your app against timing attacks, use ActiveSupport::SecurityUtils.secure_compare.
See https://codahale.com/a-lesson-in-timing-attacks/
See https://thisdata.com/blog/timing-attacks-against-string-comparison/
Also, use & (do not use &&) so that it doesn't short circuit.
And finally, use digests to stop length information leaking (default of secure_compare in Active Support 5).
So, in an initializer file, typically in config/initializers/sidekiq.rb in Rails projects, depending of your version of Active Support/Rails, write the following.
Active Support 5+: Thanks to Rails PR #24510, parameters passed to secure_compare are going through Digest::SHA256.hexdigest by default.
require 'active_support/security_utils'
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
ActiveSupport::SecurityUtils.secure_compare(user, ENV["SIDEKIQ_ADMIN_USER"]) &
ActiveSupport::SecurityUtils.secure_compare(password, ENV["SIDEKIQ_ADMIN_PASSWORD"])
end
Active Support 4:
require 'active_support/security_utils'
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(user),
::Digest::SHA256.hexdigest(ENV["SIDEKIQ_ADMIN_USER"])
) &
ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(password),
::Digest::SHA256.hexdigest(ENV["SIDEKIQ_ADMIN_PASSWORD"])
)
end
If you're using Sorcery for authentication, here's how to use Rails routes constraints to protect certain routes.
Copied here from the sorcery wiki for redundancy:
This tutorial shows how to use Rails routes constraints with Sorcery gem. Thanks to #anthonator for writing it!
First, define UserConstraint module that will be used for all constraints:
module RouteConstraints::UserConstraint
def current_user(request)
User.find_by_id(request.session[:user_id])
end
end
Then, having that module defined, you can specify specific constraint classes. In these examples, first route will work only if there's no user logged in, the second will work only for logged user who is an admin:
class RouteConstraints::NoUserRequiredConstraint
include RouteConstraints::UserConstraint
def matches?(request)
!current_user(request).present?
end
end
class RouteConstraints::AdminRequiredConstraint
include RouteConstraints::UserConstraint
def matches?(request)
user = current_user(request)
user.present? && user.is_admin?
end
end
Finally, you can add the constraints to the config/routes.rb:
MyApp::Application.routes.draw do
# other routes …
root :to => 'admin#dashboard', :constraints => RouteConstraints::AdminRequiredConstraint.new
root :to => 'home#welcome', :constraints => RouteConstraints::NoUserRequiredConstraint.new
end
Another option would be to add something like CanCan and special access based on roles.
In my Rails 3.1 app I created a Rack Middleware to verify access. If access is not approved user is to be redirected to a page. Specifically it will be a page I already have in my views. Suppose I am trying to redirect to dummy.html.erb with I have defined in my routes.rb as
match '/dummy', to :'page#dummy'
with page being my controller.
I've tried the following but I appear to be stuck in some redirect loop.
My Rack middleware located in /lib :
class AccessVerifier
def initialize(app)
#app = app
end
def call (env)
#....
#....do some type of verification here and redirect if fail verification
#....
[301, {"Location" => '/dummy', "Content-Type" => "text/html"}, []]
end
end
In application.rb I have
config.autoload_paths += %W(#{config.root}/lib)
config.middleware.use "AccessVerifier"
I also tried calling a controller in my middleware but again I am caught in some redirect loop. I called the controller from my middleware class like this:
def call (env)
...
status,headers,response=PageController.action("validateAccess").call(env)
end
and in my controller:
class PageController < ApplicationController
def validateAccess
redirect_to :controller => 'page', :action => "dummy"
end
...
end
I've seen redirecting done successfully without the use of Rack Middleware, for example only with controllers, but please note that I need to do this in middleware before my application is run.
The answer is so simple I felt silly. The problem is that when I redirect to somewhere say /dummy that in turns causes another pass through the Rack middleware and goes through my AccessVerifier code all over again which redirects to /dummy and does this over and over again thus causing the redirect loop. To fix it I force a stopping point by checking if the incoming path is included in the list of my stopping points(with /dummy being in that list) then stop. So an example in pseudo code
if path in ListOfAcceptableStoppingPoints
#app.call(env)
This fixes the redirect loop issue but now I am questioning if it would be better for my specific case to not use rake middleware as I have found that now I am filtering other things out such as assets. Sure I could try to go through and pick out everything I think I need to allow to go through but this seems too tedious and not correct. It seems like ultimately, I need to do my filtering at the rails level not at the rack level.
I don't have an answer off of the top of my head to directly answer your question. However, I would suggest using the cancan gem to handle authorization instead of creating a homegrown solution. See https://github.com/ryanb/cancan
I have a Sinatra app running on Heroku. I've had a really tough time properly setting up the cookies for my users. I've been looking at examples and documentation for a couple days now and just can't seem to crack the problem. I would like to have my users login with a email and password. If the login is successful, I want to create a cookie which expires after some set amount of time. I want the user to remain logged in if they close and re-open their browser or if I push new code to Heroku. I think that is just normal use of cookies, so if someone can help me get it going, I'd be very grateful.
Here is how I think I'm supposed to setup the post '/login' route.
post '/login/?' do
#do_login is a helper method which checks if the user's credentials are correct
if do_login
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'www.Feistie.com',
:path => '/',
:expire_after => 2592000,
:secret => 'a_30_character_key',
:user_id => params[:user_id]
end
end
Even if that is correct, I don't really understand it. What is the :key? I'm assuming :domain should just be set to my website as I did. What about :path? Why is :path set to '/'? Is there a place I actually set the secret key for my Rack app? I'm not totally clear on how cookie security works. I'm also not sure if I can add :user_id to the hash the way I did.
Then next part is setting up a helper "logged_in?" to see if someone is logged in?
def logged_in?
!session.nil?
end
That is probably wrong, but I figured I'd try.
I also need to be able to check who the current user actually is. I used to do this by session[:user_id] == #user.id or something similar. The main question I have is how do I access the :user_id value in the cookie?
Finally the logout.
post '/logout/?' do
session.clear
end
If you guys could get me on track with all this, it would be amazing! Thanks in advance and if you need more info, I will be glad to provide.
It's much simpler than that.
http://www.sinatrarb.com/faq.html#sessions
enable :sessions
post '/login/?' do
if do_login
session[:user_id] = params[:user_id]
end
end
post '/logout/?' do
session[:user_id] = nil
end
I generally try to keep session management to the business logic in get, post, before, etc blocks. You can use a before filter to "log-in" the current session's user.
before '*' do
set :user_id, session[:user_id]
end
This is completely optional, but you can put any cookie config in your rackup file:
use Rack::Session::Cookie,
:key => 'rack.session',
:path => '/',
:expire_after => 14400, # In seconds
:secret => 'change_me'
You could just try setting it with response
response.set_cookie "user_id", {:value => params['user_id'],
:expires => (Time.now + 52*7*24*60*60)}
And you can read it back any time before it expires with
request.cookies["user_id"]
To delete the cookie
response.delete_cookie "user_id"
...
I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.
In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.
I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.
Here is a very simple authentication scheme for Sinatra.
I’ll explain how it works below.
class App < Sinatra::Base
set :sessions => true
register do
def auth (type)
condition do
redirect "/login" unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
#user != nil
end
end
before do
#user = User.get(session[:user_id])
end
get "/" do
"Hello, anonymous."
end
get "/protected", :auth => :user do
"Hello, #{#user.name}."
end
post "/login" do
session[:user_id] = User.authenticate(params).id
end
get "/logout" do
session[:user_id] = nil
end
end
For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.
The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)
Finally, the before handler sets up a #user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.
Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"You're welcome"
end
I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.
I' have found this tutorial and repository with a full example, its working fine for me
https://sklise.com/2013/03/08/sinatra-warden-auth/
https://github.com/sklise/sinatra-warden-example
I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'change_me'
As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.
I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.
Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/
I have a ROR application with authlogic and LDAP, i follow the how to oh authologic and this http://pastie.org/385199 but the method (def valid_ldap_credentials?(password_plaintext)) is not called....any help ?
I don't think you want to set :validate_password_field => false. My understanding is that this disables all password validation, so your custom method won't get called. Try taking that out and see what happens.
Also, the format appears incorrect -- Authlogic configuration options need to be done within a block like so:
acts_as_authentic do |c|
c.validate_password_field = false
end