I want to make simple authentication like
# SessionController
require 'session.rb'
def create
Session.new(params[:email], params[:password])
end
# lib/session.rb
class Session
def initialize(email, password)
client = Client.find_by_email_and_password(email, password)
cookies[:user_id] = client.id if client
end
end
only problem here is that I can't use cookies or sessions out of controller. I can replace cookies setting into controller, but it isn't what I want :)
Question is how to use cookies out of controller and what are best practices.
Firstly, don't explicitly add '.rb' to a require statement. You'd just use require 'session' normally. Second, Rails will load it for you when you reference Session, so you don't need to require it explicitly. Also, client ? cookies[:user_id] = client.id is invalid Ruby code. You probably want cookies[:user_id] = client.id if client.
An obvious solution would be to simply pass the cookies object to the Session you're creating, but my recommendation would be to simply put the code in the controller. Why do you have a Session object in the first place? I'd recommend not overcomplicating things by creating an unnecessary class.
Update:
To illustrate my last comment, here's some sample code:
class Session
attr_reader :controller
def initialize(controller)
#controller = controller
end
def client
if controller.cookies[:user_id]
return Client.find_by_id(controller.cookies[:user_id])
elsif (email = controller.params[:email]) && (password = controller.params[:password])
client = Client.find_by_email_and_password(email, password)
controller.cookies[:user_id] = client.id if client
return client
end
end
end
Related
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
loop do
token = Devise.friendly_token
break token unless self.class.unscoped.where(authentication_token: token).first
end
I'm a newbie to ruby and it's rails so, hoping you guys will not downvote my question, since I google around but could not understand between differences.
The difference is User.where vs self.class.unscoped. They do the same thing essentially.
In the first construct the class name User is hardcoded into the expression. So the construct will only call where on the User class. And the User class has to respond to the where message; it must have a class method called where.
In the second construct the class name is derived. In Ruby, self refers to the instance you are currently working within. If you call self.class inside an instance, you get the class name of the instance. For example:
apple = Apple.new
puts apple.class.name
# => "Apple"
So the second construct can be used inside any class.
The last difference is the unscoped call. This is an ActiveRecord method that removes any defined scopes on a class. Think of it like "remove any filters" before finding records where authentication_token equals token.
I believe unscoped was deprecated.
Given you are in the class User e.g. in user.rb self.class maps to User. So the difference is actually another scope added to the where clause:
class User
def some_method_to_find_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
def some_other_method_to_find_token
loop do
token = Devise.friendly_token
break token unless User.unscoped.where(authentication_token: token).first
end
end
end
I guess the intention is to create a new token if no token exists. And there probably is a default scope in place which probably filters out deleted_users. Not a really good coding example actually. The first loop is unnecessary as you always have to check if this token is set to a deleted user anyways.
class User
def new_untaken_token
loop do
token = Devise.friendly_token
break token unless User.unscoped.exists?(authentication_token: token)
end
end
end
This could also be written recursivly:
class User
def new_untaken_token
token = Devise.friendly_token # Create a new token
if User.unscoped.exists?(authentication_token: token) # check if it exists
new_untaken_token # find another one
else
token # use token
end
end
end
I`m use gems 'mixpanel-ruby' && 'devise'. When user sign up, i wrote
class RegistrationsController < Devise::RegistrationsController
def create
tracker = Mixpanel::Tracker.new('MIXPANEL_ID')
email = params[:user][:email]
distinct_id = params[:user][:distinct_id]
tracker.alias(email, distinct_id)
tracker.people.set(email, {
'$email' => email,
})
tracker.track(email, "Sign up #{email}")
super
end
end
But when user open my web app from another computer, his distinct_id will other than the first. I guess need rewrite SessionController with action create, but mixpanel.identify() can be caused by javascript and not by ruby.
Who can help to understand?
You make get district_id from cookie: cookie = document.cookie.split(";")[0]; distinct_id = cookie.substr(74,53); but correctly use regexp
For some reason after some time on my website my session hash is turning into a string
undefined method `admin?' for "#<Visitor:0x000001071b7800>":String
is what I'm getting in my render_layout method
def render_layout
if session[:visitor].admin?
render layout: 'admin'
else
render layout: 'application'
end
end
the only two other times I ever call or use session[:visitor] is in my authenticate method, and my logged_in? method that i use to skip authenticate
def authenticate
uuid = params[:uuid]
#visitor ||= uuid && Visitor.find_by_uuid(uuid)
if !#visitor
authenticate_or_request_with_http_basic do |login, password|
#visitor = Visitor.find_by_uuid(ENV['ADMIN_UUID']) if login == 'test' && password == 'testpw'
end
session[:visitor] = #visitor
else
session[:visitor] = #visitor
end
end
def logged_in?
!!session[:visitor]
end
Why is this getting turned into a string? I used a project search in atom and I only ever called it in those places.
Edit:
I've added a binding.pry at the 4 locations I call session[:visitor] and it works the first time through everything. As soon as I follow a url for the first time and
before_action :authenticate, unless: :logged_in?
gets called for a second time the session[:visitor] is turned into a string
#=> "#<Visitor:0x00000106851bd0>"
From the docs, http://guides.rubyonrails.org/security.html#sessions
Do not store large objects in a session. Instead you should store them
in the database and save their id in the session. This will eliminate
synchronization headaches and it won't fill up your session storage
space (depending on what session storage you chose, see below). This
will also be a good idea, if you modify the structure of an object and
old versions of it are still in some user's cookies. With server-side
session storages you can clear out the sessions, but with client-side
storages, this is hard to mitigate.
Store your visitor's ID in the session
session[:visitor_id] = #visitor.id
and then retrieve it as needed
#visitor = User.find_by_id(session[:visitor_id])
I have RESTful API written on RoR 3.
I have to make my application not to send "Set-Cookie header" (clients are authorizing using auth_token parameter).
I have tried to use session :off and reset_session but it does not make any sense.
I am using devise as authentication framework.
Here is my ApplicationController
class ApplicationController < ActionController::Base
before_filter :reset_session #, :unless => :session_required?
session :off #, :unless => :session_required?
skip_before_filter :verify_authenticity_token
before_filter :access_control_headers!
def options
render :text => ""
end
private
def access_control_headers!
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Headers"] = "Content-type"
end
def session_required?
!(params[:format] == 'xml' or params[:format] == 'json')
end
end
Use the built in option.
env['rack.session.options'][:skip] = true
or the equivalent
request.session_options[:skip] = true
You can find the documentation for it here https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L213
As is mentioned in a comment on John's answer, clearing the session will not prevent the session cookie from being sent. If you wish to totally remove the cookie from being sent, you have to use Rack middleware.
class CookieFilter
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
# use only one of the next two lines
# this will remove ALL cookies from the response
headers.delete 'Set-Cookie'
# this will remove just your session cookie
Rack::Utils.delete_cookie_header!(headers, '_app-name_session')
[status, headers, body]
end
end
Use it by creating an initializer with the following body:
Rails.application.config.middleware.insert_before ::ActionDispatch::Cookies, ::CookieFilter
To prevent the cookie filter to end up in application stack traces, which can be utterly confusing at times, you may want to silence it in the backtrace (Assuming you put it in lib/cookie_filter.rb):
Rails.backtrace_cleaner.add_silencer { |line| line.start_with? "lib/cookie_filter.rb" }
I'm not sure when they added it to Devise, but there appears to be a configuration that will let you disable the sending of the session cookie when using a auth_token:
# By default Devise will store the user in session. You can skip storage for
# :http_auth and :token_auth by adding those symbols to the array below.
# Notice that if you are skipping storage for all authentication paths, you
# may want to disable generating routes to Devise's sessions controller by
# passing :skip => :sessions to `devise_for` in your config/routes.rb
config.skip_session_storage = [:http_auth, :token_auth]
It does work well. The only issue I had was that I still needed to be able to make an initial request to my token_controller in order to generate/retrieve the token. I.e. POST /api/v1/tokens.json, which unfortunately would cause a session cookie to be returned for that request.
So I ended up implementing the CookieFilter intializer that Ryan Ahearn wrote above anyway.
Also, since my app has both a web front-end as well as a JSON api, I only wanted to filter the cookies for the JSON api. So I modified the CookieFilter class to first check the requests belonged to the api:
if env['PATH_INFO'].match(/^\/api/)
Rack::Utils.delete_cookie_header!(headers, '_myapp_session')
end
Not sure if there's a better way of doing that...
Another solution:
In the controller you want to avoid cookies, add this:
after_filter :skip_set_cookies_header
def skip_set_cookies_header
request.session_options = {}
end
If you have a set of api controllers, set this in an api_controller class and let your other controllers inherit the api_controller.
This skips setting Set-Cookie header since the session opts is empty.
The default CookieSessionStore doesn't send a "Set-Cookie" header unless something is added to the session. Is something in your stack writing to the session? (it's probably Devise)
session :off has been deprecated:
def session(*args)
ActiveSupport::Deprecation.warn(
"Disabling sessions for a single controller has been deprecated. " +
"Sessions are now lazy loaded. So if you don't access them, " +
"consider them off. You can still modify the session cookie " +
"options with request.session_options.", caller)
end
If something in your stack is setting session info, you can clear it using session.clear like so:
after_filter :clear_session
def clear_session
session.clear
end
Which will prevent the Set-Cookie header from being sent
Further to John's answer, if you are using CSRF protection you would need to turn that off for web service requests. You can add the following as a protected method in your application controller:
def protect_against_forgery?
unless request.format.xml? or request.format.json?
super
end
end
This way HTML requests still use CSRF (or not - depends on config.action_controller.allow_forgery_protection = true/false in the environment).
I myself truly missed being able to declaratively turn off sessions (using session :off)
... thus I brought it "back" - use it just like in plain-old-rails (<= 2.2) :
than of course this might require some additional Devise specific hacking of your own, since session_off might cause session == nil in a controller, and most rails extensions since 2.3 simply assume a lazy session that shall not be nil ever.
https://github.com/kares/session_off
Imo the best approach is to simply remove the cookie session store middleware.
To do so, add this to your application.rb (or to a specific environment if needed):
# No session store
config.middleware.delete ActionDispatch::Session::CookieStore
Try this instead
after_filter :skip_set_cookies_header
def skip_set_cookies_header
session.instance_variable_set('#loaded', false)
end
Or even better, always remove Set-Cookie header when session data did not change
before_filter :session_as_comparable_array # first before_filter
after_filter :skip_set_cookies_header # last after_filter
def session_as_comparable_array(obj = session)
#session_as_comparable_array = case obj
when Hash
obj.keys.sort_by(&:to_s).collect{ |k| [k, session_as_comparable_array(obj[k])] }
when Array
obj.sort_by(&:to_s).collect{ |k| session_as_comparable_array(k) }
else
obj
end
end
def skip_set_cookies_header
session.instance_variable_set('#loaded', false) if (#session_as_comparable_array == session_as_comparable_array)
end
# frozen_string_literal: true
module Api
module Web
module Base
class WebApiApplicationController < ApplicationController
include DeviseTokenAuth::Concerns::SetUserByToken
include Api::Concerns::ErrorsConcern
devise_token_auth_group :user, contains: %i[api_web_v1_user]
respond_to :json
serialization_scope :current_user
before_action :METHOD_NAME
private
def METHOD_NAME
request.session_options[:skip] = true
end
end
end
end
end
It's working for me.
I am trying to create a session explicitly like this UserSession.create(#user, true) but the session is not getting created, current_user is nil.
But when I do this, I get < #UserSession: {:unauthorized_record=>""}>us = UserSession.create(#user, true)
RAILS_DEFAULT_LOGGER.info(us.inspect) #=> UserSession: {:unauthorized_record=>""}
I had a look at Authlogic::Session::UnauthorizedRecord here it says
Be careful with this, because Authlogic is assuming that you have already confirmed that the user is who he says he is. For example, this is the method used to persist the session internally. Authlogic finds the user with the persistence token. At this point we know the user is who he says he is, so Authlogic just creates a session with the record. This is particularly useful for 3rd party authentication methods, such as OpenID. Let that method verify the identity, once it’s verified, pass the object and create a session.
which is exactly what I am trying to do (i am authenticating using omniauth and creating session using authlogic).
How do I fix this, so that I can get a valid session in current_user ?
I had a similar issue caused by the persistence_token being nil on the user. Reset it before creating the UserSession. So...
#user.reset_persistence_token!
UserSession.create(#user, true)
I'm not sure about the .create(object, bool) method signature, but the following works using authlogic.
class Api::ApiBaseController < ApplicationController
protected
def verify_token
return false if params[:token].blank?
#session = UserSession.new(User.find_by_single_access_token(params[:token]))
#session.save
end
end
If that doesn't work for you -- I think the #user isn't being set correctly.
If you map the active_record_store to the authlogic user_sessions table your session information will be stored in the database, and you will be able to store larger sets of data.
Inside your config folder:
config/initializers/session_store.rb
Comment out App::Application.config.session_store :cookie_store, :key => '_App_session'
Add or uncomment App::Application.config.session_store :active_record_store
Inside of config/application.rb
At the end of the class for you application add:
ActiveRecord::SessionStore::Session.table_name = 'user_sessions'
Restart your app, and any information stored in the user session will be saved in the authlogic user_sessions table.
Goto: http://apidock.com/rails/ActiveRecord/SessionStore
For more information
For now you can replace
UserSession.create #user
to
UserSession.create :email => #user.email, :password => #user.password
not a big deal.
But that caught me other way. I forgot that my user got active? == false when created. I've set it to true and session is created.
I ran into this problem today. In my case it ended up being related to CSRF tokens.
We are creating a user and session in our app in response to an OAuth callback. It appears that if the CSRF token is invalid, which would be the case when coming from a third party, authlogic won't create the user session.
Can't verify CSRF token authenticity
The fix was simple:
class Oauth::UserSessionsController < ApplicationController
skip_before_action :verify_authenticity_token, only: :callback
def new
# code removed...
end
def callback
# code removed...
UserSession.create(#user)
redirect_to root_path
end
end