I am currently using Clearance from Throughbot for my authentication. I am needing to add an API to my product and can't seem to find docs about using Clearance with an API. Is there a certain Header I can set that Clearance will check automatically and if not what can I use? I think I may be able to use this.
To get around this I ended up overriding the authenticate methods on the ApplicationController and the User model. It looks something like this:
class ApplicationController < ActionController::Base
include Clearance::Controller
include Clearance::Authentication
def authenticate(params)
if request.headers['AUTH-TOKEN']
return nil unless user = User.where(remember_token: request.headers['AUTH-TOKEN']).first
sign_in user
else
User.authenticate(params[:session][:email], params[:session][:password])
end
end
#rest of class omitted for bevity
end
Then I subclasses SessionsController to override the create method like so:
class SessionsController < Clearance::SessionsController
def create
#user = authenticate(params)
sign_in(#user) do |status|
respond_to do |format|
if status.success?
format.html { redirect_back_or url_after_create }
format.json { render json: #user, status: :ok }
else
format.html do
flash.now.notice = status.failure_message
render template: 'sessions/new', status: :unauthorized
end
format.json { render json: [errors: status.failure_message], status: :unauthorized }
end
end
end
end
#rest of class omitted for bevity
end
Then all you have to do to test or use is set the requests AUTH-TOKEN header to the users remember token and you're all set. I chose to use the remember token because it is updated whenever the user logs out. You may not want this to happen and could instead generate a auth_token field on your model and change the where to use the new field.
Related
I am working on rails and trying to make a simple blog site and its working the way i want to on my local machine but when pushed to production its being blocked by the callback functions.
My before_action :authorized_user? callback is being called and it prompts for logging if not logged in for performing any method on the blog , and if logged in all methods create, update and destroy methods are working perfectly in my development environment but in production even after the user is logged in also and when the create method is being called it asks for to log in . I am unable to understand from where or what code is causing this to happen because the same is working perfectly fine on local machine.
Any help will he highly appreciated.
My blog_controller.rb file is
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :update, :destroy, :lock_blog, :pin_blog]
before_action :authorized_user?, except: [:index, :show]
def index
#blogs = Blog.all
render json: { blogs: #blogs },status: :ok
end
def show
comments = #blog.comments.select("comments.*, users.username").joins(:user).by_created_at
render status: :ok, json: { blog: #blog, blog_creator: #blog.user, comments: comments }
end
def create
#blog = Blog.new(blog_params.merge(user_id: #current_user.id))
if authorized?
if #blog.save
render status: :ok,
json: {blog: #blog , notice: "Blog Successfully created"}
else
errors = #blog.errors.full_messages.to_sentence
render status: :unprocessable_entity, json: {error:errors}
end
end
end
def update
if authorized?
if #blog.update(blog_params)
render status: :ok,
json: {blog: #blog, notice:"Blog successfully updated"}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
def destroy
if authorized?
if #blog.destroy
render status: :ok,
json: {notice:'Blog deleted'}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
private
def set_blog
#blog = Blog.find(params[:id])
end
def blog_params
params.require(:blog).permit(:title,:body,:image,:is_pinned, :is_locked)
end
def authorized?
#blog.user_id == #current_user.id || #current_user.admin_level >= 1
end
def handle_unauthorized
unless authorized?
render json:{notice:"Not authorized to perform this task"}, status:401
end
end
end
and application_controller.rb file is
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include CurrentUserConcern
include ExceptionHandlerConcern
include TokenGenerator
def authorized_user?
render json: { notice: 'Please log in to continue' }, status: :unauthorized unless #current_user
end
def authorized_admin?
authorized_user?
render json: {errors: 'Insufficient Administrative Rights'}, status: 401
end
private
end
current_user_concern.rb file
module CurrentUserConcern
extend ActiveSupport::Concern
included do
before_action :set_current_user
end
def set_current_user
if session[:token]
#current_user = User.find_by(token: session[:token])
end
end
end
Its generally recommended to use libraries for authentication and authorization instead of reinventing the wheel unless its for learning purposes. They have many eyes looking for bugs and insecurites and are battle hardened by tons of users. Home-rolled authentication systems are a very common source of security breaches which could lead to very expensive consequences.
If you're going to roll your own authorization and authentication solution I would suggest you take a page from the libraries like Devise, Pundit and CanCanCan and raise an error when a user is not authorized or authenticated so that you immediately halt whatever the controller is doing and stop the callback chain from executing further.
# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end
# app/controllers/concerns/
module Authenticable
extend ActiveSupport::Concern
included do
helper_method :current_user, :user_signed_in?
before_action :authenticate_user
rescue_from AuthenticationError, with: :handle_unauthorized
end
def current_user
#current_user ||= find_user_from_token if session[:token].present?
end
def find_user_from_token
User.find_by(token: session[:token])
end
def user_signed_in?
current_user.present?
end
def authenticate_user
raise AuthenticationError.new('Please log in to continue') unless user_signed_in?
end
def handle_unauthenticated(error)
render json: {
notice: error.message
},
status: :unauthorized
end
end
end
# app/controllers/concerns/authorizable.rb
module Authorizable
extend ActiveSupport::Concern
included do
rescue_from AuthenticationError, with: :handle_unauthorized
end
def authorize_admin
raise UserAuthenticationError.new('Insufficient Administrative Rights') unless current_user.admin?
end
def handle_unauthorized(error)
render json:{
notice: error.message
}, status: :unauthorized
end
end
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include Authenticable
include Authorizable
# Should you really be mixing this into the controller? Seperate the responsibilites!
include TokenGenerator
end
It also makes debugging much easier as you can disable rescue_from in testing so that you get an exception instead of just a cryptic failure message.
You should also setup your authorization system so that it always authenticates (and authorizes) unless you explicitly opt out. This is a best practice that reduces the possible of security breaches simply due to programmer omission. You opt out by calling skip_before_action :authorize_user.
Instead of your set_current_user use a memoized getter method (current_user) to remove issues caused by the ordering of callbacks. ||= is conditional assignment and will prevent it from querying the database again if you have already fetched the user. This should be the ONLY method in the system that knows how the user is stored. Do not access #current_user directly to avoid leaking the implementation details into the rest of the application.
Methods ending with ? are by convention predicate methods in Ruby and should be expected to return a boolean. Name your modules by what their responsibility is and not what code they contain - avoid the postfix Concern as it tells you nothing about what it does.
I am trying to learn Rails and have created User model using devise but just using Rails an API. I have added custom validations on the controller and it works fine. The user is able to login when posting data from the front-end. But, the problem is that the browser doesn't store the session anywhere. Hence, the session will be considered new every-time a page is reloaded. I am wondering if devise has an in-built functionality which would create session and store in cookie or local storage.
class API::V1::SessionsController < Devise::SessionsController
def create
#user = User.find_by_email(user_params[:email])
if #user && #user.valid_password?(user_params[:password])
sign_in :user, #user
render json: #user
elsif #user && not(#user.valid_password?(user_params[:password]))
invalid_attempt
else
no_user
end
end
def destroy
#message = "signed out"
sign_out(#user)
render json: #message
end
private
def no_user
render json: {error: "An account with this email doesn't exist. Please create a new one"}, status: :unprocessable_entity
end
def invalid_attempt
render json: { error: "Your password isn't correct" }, status: :unprocessable_entity
end
def user_params
params.require(:user).permit(:email, :password)
end
end
From the frontend sends a request for the establishment of Taska and I receive here such error:
There is another error on the server:
In the console itself, I get:
def create
#task = current_user.tasks.new(task_params) // It's 19 line tasks_controller
if #task.save
render json: #task, status: :created, location: #task
else
render json: #task.errors, status: :unprocessable_entity
end
end
and
private
def task_params
params.require(:task).permit(:title, :body)
end
current_user - application_controller.rb
def current_user
current_user ||= User.find_by(token: request.headers['Authorization'])
end
Scheme table users.
I am new to all this, what is obvious to you is not known to me, therefore I am here.
This is the classic do-it-yourself authentication nil error. When setting up an authentication system you should ensure that any action that requires the user to be signed in will bail early and redirect the user to the sign in or if its an API send a header that indicates that the user is not authorized.
class AuthenticationError < StandardError; end
class ApplicationController
# locking everything down makes your app secure by default
# use skip_before_action :authenticate_user! to allow unauthorized users
before_action :authenticate_user!
rescue_from AuthenticationError, with: :handle_unauthorized_access
private
def authenticate_user!
raise AuthenticationError unless current_user
end
def handle_unauthorized_access
respond_to do |f|
f.html { redirect_to '/path/to/login', notice: 'Please sign in' }
f.json { head :unauthorized }
end
end
# ...
end
Even better is to not reinvent the wheel. Authentication is hard and we all screw it up. Thats why its good to use libraries like Devise or Knock that have tons of eyes reviewing the code.
I want to send a email to user after create something via rails admin.
I know I can call it in model callback but it's not considered as a good pratices
the best way is to put the actionmailer action after model save in the controller but I don't know how to do it in rails_admin controller
class UsersController < ApplicationController
# POST /users
# POST /users.json
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
# Tell the UserMailer to send a welcome email after save
UserMailer.welcome_email(#user).deliver_later
format.html { redirect_to(#user, notice: 'User was successfully created.') }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
end
What you want to do is not easy on rails admin because you cannot modify the controllers nor do you have access to them without monkey patching them.
I actually made a fork of rails admin for this functionality checkout the commit with the changes:
https://github.com/aliada-mx/rails_admin/commit/6251554efd1d83cdb418f42683ee55a4e27c2474
Just touched two files
And example usage
class User
after_save :on_admin_updates
attr_accessor :edited_in_rails_admin
def on_admin_updates
return unless edited_in_rails_admin
self.edited_in_rails_admin = false
UserMailer.welcome_email(self.id)
end
end
A bit clunky i know, PR´s welcome.
Have you tried to include your Mailer in admin_controller?
include UserMailer
Then in your create action UserMailer.some_mailer_action.deliver_now
Is there an easy way to write a helper method to always update the previously visited url in the session. I have tried the method below but the url saved is always the current one. I would like to be able to use this helper in all my controllers for redirect.
#application_controller.rb
class ApplicationController < ActionController::Base
before_filter :my_previous_url
def my_previous_url
session[:previous_url] = request.referrer
end
helper_method :my_previous_url
end
I have used it in my update method in the User controller as seen below but it always redirects to the same opened url (kind of looks like refresh was hit).
def update
if current_user.admin == true and #user.update(user_params)
redirect_to my_previous_url, notice: "Password for User #{#user.username} has Successfully been Changed."
return
elsif current_user.admin == false and #user.update(user_params)
session[:user_id] = nil
redirect_to login_path, notice: "Password for User #{#user.username} has Successfully been Changed. Please Log-In Using the New Password."
return
end
respond_to do |format|
if #user.update(user_params)
changed = true
format.html { redirect_to logout_path }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
request.referer isn't what you want here as it will be set on page redirects, thus losing the page you came from originally. I think that you have an implied requirement that it should return the last visited url which was different to the current one, is that the case? Also, i think that you would only want to set it for GET requests, otherwise you risk sending people back to the wrong url, since they will be sent back with a GET request. I'm assuming here that the purpose of this previous_url is to give people a "back" link.
Also, don't get the method to set the previous_url mixed up with the method to read it back out again.
I would do it like this:
#application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_previous_url
helper_method :previous_url
def set_previous_url
if request.method == :get && session[:previous_url] != session[:current_url]
session[:previous_url] == session[:current_url]
session[:current_url] = request.url
end
end
def previous_url
session[:previous_url]
end
end