I'm having trouble getting my session on Rails 7 to persist. I'm not even redirecting at any point, just staying on the one page.
application_controller.rb
class ApplicationController < ActionController::API
include ActionController::Cookies
end
application.rb
require_relative "boot"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module CsmBack
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Must add these lines!
# Adding back cookies and session middleware
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore, key: '_namespace_key'
# Use SameSite=Strict for all cookies to help protect against CSRF
config.action_dispatch.cookies_same_site_protection = :strict
end
end
sessions_controller.rb
def login
if session[:user_id]
return render json: {errors: "Username or Password Wrong"}
end
user=User.find_by(username: params[:username])
if user&.authenticate(params[:password])
session[:user_id] = user.id
render json: user
else
render json: {errors: "Username or Password Wrong"}
end
end
If that last one worked, I should be able to click the button to trigger it twice in a row and get different values, but I'm not even getting that.
You seem to be throwing an error to the client side if they try to log in without logging out first.
According to what I see here, your server-side code, (however off it might seem) is fine. You might want to look at your client side, maybe set up a proxy
Related
Using devise gem to authenticate all users of an application.
I'm trying to implement Active Storage.
Let's say that all users must be authenticated as soon as they reach the app:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
...
end
How to secure the Active Storage generated routes?
URL of an uploaded file can be accessed without having to authenticate first. The unauthenticated user can get the file url generated by Active Storage.
This is not a full answer but a starting point:
The gist: You would need to override the redirect controller.
The docs for activestorage/app/controllers/active_storage/blobs_controller.rb say:
If you need to enforce access protection beyond the
security-through-obscurity factor of the signed blob references,
you'll need to implement your own authenticated redirection
controller.
Also if you plan to use previews the docs for activestorage/app/models/active_storage/blob/representable.rb say
Active Storage provides one [controller action for previews], but you may want to create your own (for
example, if you need authentication).
Also you might find some relevant information in this rails github issue
Update:
Here is a minimal example that "should" work for preventing unauthorised access to the redirects when using the devise gem.
How the url, that the user will be redirected to if logged, is then secured is still another story I guess. By default they expire after 5 minutes but this could be set to a shorter period like 10 seconds (if you replace line 6 in example below with expires_in 10.seconds)
Create a file app/controllers/active_storage/blobs_controller.rb with the following code:
class ActiveStorage::BlobsController < ActiveStorage::BaseController
before_action :authenticate_user!
include ActiveStorage::SetBlob
def show
expires_in ActiveStorage::Blob.service.url_expires_in
redirect_to #blob.service_url(disposition: params[:disposition])
end
end
Please note that the only thing that changed from the original code is that the second line is added
before_action :authenticate_user!
Update 2:
Here is a concern that you can include in ActiveStorage::RepresentationsController and ActiveStorage::BlobsController to enable devise authentication for ActiveStorage
See gist is at https://gist.github.com/dommmel/4e41b204b97238e9aaf35939ae8e1666 also included here:
# Rails controller concern to enable Devise authentication for ActiveStorage.
# Put it in +app/controllers/concerns/blob_authenticatable.rb+ and include it when overriding
# +ActiveStorage::BlobsController+ and +ActiveStorage::RepresentationsController+.
#
# Optional configuration:
#
# Set the model that includes devise's database_authenticatable.
# Defaults to Devise.default_scope which defaults to the first
# devise role declared in your routes (usually :user)
#
# blob_authenticatable resource: :admin
#
# To specify how to determine if the current_user is allowed to access the
# blob, override the can_access_blob? method
#
# Minimal example:
#
# class ActiveStorage::BlobsController < ActiveStorage::BaseController
# include ActiveStorage::SetBlob
# include AdminOrUserAuthenticatable
#
# def show
# expires_in ActiveStorage::Blob.service.url_expires_in
# redirect_to #blob.service_url(disposition: params[:disposition])
# end
# end
#
# Complete example:
#
# class ActiveStorage::RepresentationsController < ActiveStorage::BaseController
# include ActiveStorage::SetBlob
# include AdminOrUserAuthenticatable
#
# blob_authenticatable resource: :admin
#
# def show
# expires_in ActiveStorage::Blob.service.url_expires_in
# redirect_to #blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
# end
#
# private
#
# def can_access_blob?(current_user)
# #blob.attachments.map(&:record).all? { |record| record.user == current_user }
# end
# end
module BlobAuthenticatable
extend ActiveSupport::Concern
included do
around_action :wrap_in_authentication
end
module ClassMethods
def auth_resource
#auth_resource || Devise.default_scope
end
private
def blob_authenticatable(resource:)
#auth_resource = resource
end
end
private
def wrap_in_authentication
is_signed_in_and_authorized = send("#{self.class.auth_resource}_signed_in?") \
& can_access_blob?(send("current_#{self.class.auth_resource}"))
if is_signed_in_and_authorized
yield
else
head :unauthorized
end
end
def can_access_blob?(_user)
true
end
end
If you want to implement authentication for all endpoints provided by active storage, you can override the ActiveStorage::BaseController based on the original implementation:
# app/controllers/active_storage/base_controller.rb
# frozen_string_literal: true
# The base class for all Active Storage controllers.
class ActiveStorage::BaseController < ActionController::Base
before_action :authenticate_user!
include ActiveStorage::SetCurrent
protect_from_forgery with: :exception
end
I am attempting to set up a basic sessions-based authentication strategy in a Rails 5 API.
I think I'm mostly running into a configuration confusion, because a new session is generated on each request.
I've added the cookies and cookie store middleware back into the application
config/application.rb
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.api_only = false
end
It seems like I have to set api_only to false or I get #<ActionDispatch::Request::Session:0x7fd40b2eec80 not yet loaded>
I added the session_store initializer and the cookie_serializer:
config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: '_tunr_sol_json_api_session'
config/initializers/cookie_serializer.rb
Rails.application.config.action_dispatch.cookies_serializer = :json
I'm not storing sessions in the database.
I have a sessions controller that sets a current_user_id key to the sessions object when the user is successfully authenticated.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by(username: params[:user][:username])
if user && user.authenticate(params[:user][:password])
session[:current_user_id] = user.id
render json: {status: 201, message: "session created", user: user}
else
render json: {status: 401, message: "unauthorized"}
end
end
def destroy
session[:current_user_id] = nil
render json: {status: 204, message: "no content"}
end
end
The behavior
The auth strategy I have set up with bcrypt and has_secure_password works. And in looking at the session in sessions#create it successfully sets the user id to the session.
But the session doesn't seem to persist or get stored in the browser. sessionStorage after login has a length of 0
When I log out and check the session, it creates a new session.
Question
Not sure what configuration I'm missing or have wrong to get this functionality set up with the api.
You also need to change your Application controller to:
class ApplicationController < ActionController::Base
I created a job like so:
class SendEmailJob < ActiveJob::Base
queue_as :default
def perform(user)
#user = user
UserMailer.welcome_email(#user).deliver_later
end
end
That uses my mailer:
class UserMailer < ActionMailer::Base
def welcome_email(user)
#user = user
mg_client = Mailgun::Client.new ENV['api_key']
message_params = {
:from => ENV["gmail_username"],
:to => #user.email,
:subject => "Welcome",
:text => "This is a welcome email"
}
mg_client.send_message ENV["domain"], message_params
end
end
My controller:
SendEmailJob.set(wait: 20.seconds).perform_later(#user)
I keep getting the following error:
NoMethodError (undefined method `set' for SendEmailJob:Class):
EDIT config/application.rb
require File.expand_path('../boot', FILE)
require 'rails/all'
require 'active_job'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module LinkbuilderPro
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
end
end
Rails 4.1.8
ActiveJob was integrated with Rails from version 4.2
Before that, you needed to use the active_job gem. As you are using Rails version 4.1.8, you have to use the active_job gem along with the old syntaxes. .set method was not available before Rails 4.2, so you are getting that error.
However, the syntax for Rails version 4.1 is:
YourJob.enqueue(record)
YourJob.enqueue(record, options)
So, in your case, it would be something like:
SendEmailJob.enqueue(#user, wait: 20.seconds)
perform_later is introduced in Rails 4.2
See this article for the active job differences between Rails 4.1 and 4.2
Try this in your controller:
SendEmailJob.new(#user).enqueue(wait: 20.seconds)
Using Rails 4, I am having issues getting Authlogic to see my faked UserSession.
I have set up pages#whoami to render the current user's email address, as a simplistic test.
class PagesController < ApplicationController
# before_filter :require_user
def whoami
render :text => current_user.try(:email) || 'anonymous'
end
end
in spec/spec_helper.rb:
require "authlogic/test_case"
include Authlogic::TestCase
and my rspec test:
require 'spec_helper'
describe '/whoami' do
setup :activate_authlogic
it "should tell me who I am" do
user = FactoryGirl.create(:user)
user.should be_valid
session = UserSession.create(user)
session.should be_valid
get '/whoami'
response.body.should == user.email
end
end
I updated my application controller to show the current session:
def require_user
unless current_user
raise "Current User Session is: #{ current_user_session.inspect}"
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to new_user_session_url
return false
end
end
With before_filter :require_user commented, I correctly get "anonymous". When I uncomment it, I see that my user session is nil. I tried looking through the authlogic code but got lost in Authlogic::Session:Persistence::InstanceMethods#persisting?
I'm trying to debug. Here's where I am so far.
Here, we try to set Authlogic::Session::Base.controller to the test's mock controller:
https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/test_case.rb#L109
in my spec, I see that #controller is a Authlogic::TestCase::MockController
and in my spec, I see that Authlogic::Session::Base.controller is set to that Mock Controller.
However, I then check this:
class ApplicationController < ActionController::Base
...
def current_user_session
raise Authlogic::Session::Base.controller.inspect
...
end
end
and I see Authlogic::ControllerAdapters::RailsAdapter ... so somehow the controller is being set but isn't persisting. I'm wondering whether this has to do with the switch from Rails3 to Rails4?
Any insight into this would be appreciated.
Gem versions for those who are interested:
gem rspec-core (2.14.5)
gem authlogic (3.3.0)
gem rails (4.0.0)
Per https://stackoverflow.com/a/5803121, a request spec is just a thin wrapper around ActionDispatch::IntegrationTest. As such, there is no direct access to the session, unlike a normal controller spec.
Due to this, it isn't directly possible to log a user in directly with AuthLogic, which does rely on the session and cookies:
It first authenticates, then it sets up the proper session values and cookies to persist the session.
For request/integration/api/feature specs, a request directly to the login path will be necessary to set the proper session / cookies behind the scenes. The integration session will then be sent back (just like a normal web request) with the proper values.
To make life easier you can add a helper method, which you can include for request/integration/api/feature specs:
# spec/support/auth_logic_helpers.rb
module Authlogic
module TestHelper
# You can call this anything you want, I chose this name as it was similar
# to how AuthLogic calls it's objects and methods
def create_user_session(user)
# Assuming you have this defined in your routes, otherwise just use:
# '/your_login_path'
post user_session_path, login: user.login, password: user.password
end
end
end
# Make this available to just the request and feature specs
RSpec.configure do |config|
config.include Authlogic::TestHelper, type: :request
config.include Authlogic::TestHelper, type: :feature
end
I am building a Rails application with Omniauth for log in service.To authenticate Google I am using OmniAuth Google OAuth2 Strategy.
When user clicks 'allow access' button everything works fine.But when user clicks 'no thanks' button the below error is raised.
OmniAuth::Strategies::OAuth2::CallbackError
I have tried adding the below rescue code in application controller.
class ApplicationController < ActionController::Base
rescue_from OmniAuth::Strategies::OAuth2::CallbackError, :with =>
:omniauth_callback_error_handler
protected
def omniauth_callback_error_handler
redirect_to init_sign_in_users_path
end
end
But no luck. Any idea?
You can set the on_failure proc in the omniauth initializer in an even cleaner fashion:
OmniAuth.config.on_failure = UsersController.action(:oauth_failure)
This happens because the authentication happens in a middleware so your controller is not involved in it. This is where the exception is raised and the called code is this
I think you can handle this kind of error by defining a callback in OmniAuth initializer with this kind of code
OmniAuth.config do |config|
config.on_failure do
# your handling code invoked in the context of a rack app
end
end
Otherwise there is a commit of three months ago which introduce this behavior
def redirect_to_failure
message_key = env['omniauth.error.type']
new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{message_key}"
Rack::Response.new(["302 Moved"], 302, 'Location' => new_path).finish
end
which states that on errors your user is redirected to /auth/failure with an error message, so you should be able to define a route for that path and handle it in your app. Keep in mind that this won't happen in development mode so you need to try it in other envs. If this doesn't happen in production try to upgrade your omniauth gem to version 1.1.0
I have solved this problem with the Fabio's first suggestion.
OmniAuth.config.on_failure = Proc.new do |env|
UsersController.action(:omniauth_failure).call(env)
#this will invoke the omniauth_failure action in UsersController.
end
In my UsersController
class UsersController < ActionController::Base
def omniauth_failure
redirect_to init_sign_in_users_path
#redirect wherever you want.
end
end
There's a configuration to use /auth/failure instead of raising an error.
I use OmniAuth 1.2.2 and when I checking the FailureEndpoint I found the code is like this:
def call
raise_out! if OmniAuth.config.failure_raise_out_environments.include?(ENV['RACK_ENV'].to_s)
redirect_to_failure
end
And the failure_raise_out_environments is defined here:
def self.defaults
#defaults ||= {
# other configurations
:failure_raise_out_environments => ['development']
}
end
The environment can be configured so the solution is easy. I use Rails so I put below code in an initializer file:
OmniAuth.configure do |config|
# Always use /auth/failure in any environment
config.failure_raise_out_environments = []
end