I have an application that is using both Devise and Knock. It is using Devise to power the authentication for Active Admin and Knock gem is providing the authentication for my API's
The issue I have is that Knock can't seem to find current_user and I believe this is likely because I am using Devise in the same project.
I have the following setup:
Api Controller
class ApiController < ActionController::API
include Knock::Authenticable
end
User Controller (for API not ActiveAdmin)
module Api
module V1
class UsersController < ApiController
before_action :set_user, only: [:show, :update, :destroy]
# GET /users/1
def show
render json: #user
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
render json: #user.id, status: :created
else
render json: #user.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /users/1
def update
if #user.update(user_params)
render json: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
# DELETE /users/1
def destroy
#user.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
end
end
Auth Controller
module Api
module V1
class AuthController < ApiController
def auth
render json: { status: 200, user: current_user }
end
end
end
end
Current User in this Auth controller returns nothing however in another project I have, without devise, this will correctly return the user.
Is there a way to redefine what current_user is or assign it to something different for the purposes of using Knock?
Try this in your ApplicationController
# JWT: Knock defines it's own current_user method unless one is already
# defined. As controller class is cached between requests, this method
# stays and interferes with a browser-originated requests which rely on
# Devise's implementation of current_user. As we define the method here,
# Knock does not reimplement it anymore but we have to do its thing
# manually.
def current_user
if token
#_current_user ||= begin
Knock::AuthToken.new(token: token).entity_for(User)
rescue
nil
end
else
super
end
end
private
# JWT: No need to try and load session as there is none in an API request
def skip_session
request.session_options[:skip] = true if token
end
# JWT: overriding Knock's method to manually trigger Devise's auth.
# When there is no token we assume the request comes from the browser so
# has a session (potentially with warden key) attached.
def authenticate_entity(entity_name)
if token
super(entity_name)
else
current_user
end
end
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.
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'm using Pundit with Rails, and I have a controller that I need to completely restrict from a specific user role. My roles are "Staff" and "Consumer." The staff should have full access to the controller, but the consumers should have no access.
Is there a way to do this that is more DRY than restricting each action one-by-one?
For instance, here is my policy:
class MaterialPolicy < ApplicationPolicy
attr_reader :user, :material
def initialize(user, material)
#user = user
#material = material
end
def index?
user.staff?
end
def show?
index?
end
def new?
index?
end
def edit?
index?
end
def create?
index?
end
def update?
create?
end
def destroy?
update?
end
end
And my controller:
class MaterialsController < ApplicationController
before_action :set_material, only: [:show, :edit, :update, :destroy]
# GET /materials
def index
#materials = Material.all
authorize #materials
end
# GET /materials/1
def show
authorize #material
end
# GET /materials/new
def new
#material = Material.new
authorize #material
end
# GET /materials/1/edit
def edit
authorize #material
end
# POST /materials
def create
#material = Material.new(material_params)
authorize #material
respond_to do |format|
if #material.save
format.html { redirect_to #material, notice: 'Material was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /materials/1
def update
authorize #material
respond_to do |format|
if #material.update(material_params)
format.html { redirect_to #material, notice: 'Material was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /materials/1
def destroy
authorize #material
#material.destroy
respond_to do |format|
format.html { redirect_to materials_url, notice: 'Material was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_material
#material = Material.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def material_params
params.require(:material).permit(:name)
end
end
Is there a way to do this that I'm not understanding, or is that how Pundit is designed, to require you to be explicit?
The first step is just to move the call to authorize to your callback:
def set_material
#material = Material.find(params[:id])
authorize #material
end
You can also write #material = authorize Material.find(params[:id]) if your Pundit version is up to date (previous versions returned true/false instead of the record).
Pundit has a huge amount of flexibility in how you choose to use it. You could for example create a separate headless policy:
class StaffPolicy < ApplicationPolicy
# the second argument is just a symbol (:staff) and is not actually used
def initialize(user, symbol)
#user = user
end
def access?
user.staff?
end
end
And then use this in a callback to authorize the entire controller:
class MaterialsController < ApplicationController
before_action :authorize_staff
# ...
def authorize_staff
authorize :staff, :access?
end
end
Or you can just use inheritance or mixins to dry your policy class:
class StaffPolicy < ApplicationPolicy
%i[ show? index? new? create? edit? update? delete? ].each do |name|
define_method name do
user.staff?
end
end
end
class MaterialPolicy < StaffPolicy
# this is how you would add additional restraints in a subclass
def show?
super && some_other_condition
end
end
Pundit is after all just plain old Ruby OOP.
Pundit doesn't require you to be explicit, but it allows it. If the index? method in your policy wasn't duplicated, you'd want the ability to be explicit.
You can start by looking at moving some of the authorization checks into the set_material method, that cuts down over half of the checks.
The other half could be abstracted out into other private methods if you wanted, but I think they're fine as-is.
You could also look at adding a before_action callback to call the authorizer based on the action name, after you've memoized #material via your other callback, but readability is likely to suffer.
Use the second argument for the authorize method. Eg:
authorize #material, :index?
You can now remove all the other methods that just calls index?
I have my Banks Controller
class Api::V1::BanksController < ApplicationController
before_action :authenticate_user!
respond_to :json
# PUT /api/v1/banks/:id.json
def update
#bank = UserBank.find_by!(uuid: params[:id])
if #bank.update_attributes bank_params
render json: #bank
else
render json: #bank.errors, status: :unprocessable_entity
end
end
private
def bank_params
params.require(:bank).permit(:iban, :bic)
end
end
I'm using devise for the authentication. My problem comes from the fact that any users can update another user's bank object just by getting the access-token from the login response.
Is there a clean/secure/automatic way of preventing a user to interact with somebody else's details ?
or should I just make sure that the bank object I'm updating belongs to the logged-in user ?
thanks a lot
Your mixing up authentication and authorization.
Authentication is concerned with the identity of the user.
Authorization is a set of rules for who is allowed to do what in your application.
Devise provides authentication, you can either create your own authorization system or use a library (recommended) such as Pundit or CanCanCan.
A hacky home rolled authorization check would look like:
class AuthorizationError < StandardError; end
class ApplicationController
rescue_from AuthorizationError, with: :deny_access
def deny_access
head :unauthorized and return
end
end
class Api::V1::BanksController < ApplicationController
before_action :authenticate_user!
before_action :set_bank!
before_action :authorize!
respond_to :json
# PUT /api/v1/banks/:id.json
def update
#bank = UserBank.find_by!(uuid: params[:id])
respond_with #bank.update(bank_params)
end
private
def set_bank!
#bank = UserBank.find_by!(uuid: params[:id])
end
def authorize!
# this is the authorization rule.
unless #bank.user == current_user
raise AuthorizationError
end
end
def bank_params
params.require(:bank).permit(:iban, :bic)
end
end
I have used the Pundit Gem before, but I've never tried doing what I'm trying to do now, and for some reason Pundit is not happy.
What I'm aiming to do, is to have a modal with the 'create' (Foo) form on my 'index'(Foos) page. Thus I need to instantiate an empty Foo object for the modal form to work.
The issue that I'm experiencing, is that Pundit throws an error when I submit the form remotely. The error is:
Pundit::NotDefinedError - unable to find policy of nil
I have tried to understand why this is happening but I've not been able to solve it yet.
Here is my foos_controller.rb#index:
...
def index
#foo = Foo.new
authorize #foo, :new?
#foos = policy_scope(Foo)
end
...
I then have the following 'before_action' filter that runs for my other actions i.e. 'create'
...
before_action :run_authorisation_check, except: [:index]
def run_authorisation_check
authorize #foo
end
...
The policies that I'm using in foo_policy.rb:
....
def index?
user.has_any_role? :super_admin
end
def create?
user.has_any_role? :super_admin
end
def new?
create?
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.has_any_role? :super_admin
scope.all
end
end
end
....
The error does not present itself until I submit the form. Could anybody familiar with Pundit please help guide me to understand what I'm doing incorrectly?
UPDATE
Full foos_controller.rb
class FoosController < ApplicationController
def index
#foo = Foo.new
authorize #foo, :create?
#foos = policy_scope(Foo)
end
def new
#foo = Foo.new
end
def create
#foo = Foo.new(foo_params)
respond_to do |format|
if #foo.save
flash[:notice] = I18n.t("foo.flash.created")
format.json { render json: #foo, status: :ok }
else
format.json { render json: #foo.errors, status: :unprocessable_entity }
end
end
end
private
before_action :run_authorisation_check, except: [:index]
def foo_params
params.fetch(:foo, {}).permit(:bar)
end
def run_authorisation_check
authorize #foo
end
end
Yeah, you're not setting the value of #foo, that's why you're getting the error unable to find policy of nil.
Most times, you would have something like this in your foos_controller.rb:
before_action :set_foo, only: [:show, :edit, :update, :destroy]
before_action :run_authorisation_check, except: [:index]
...
private
def set_foo
#foo = Foo.find(params[:id])
end
Let me know if that works
I had this issue when working on a Rails 6 API only application with the Pundit gem.
I was running into the error below when I test my Pundit authorization for my controller actions:
Pundit::NotDefinedError - unable to find policy of nil
Here's how I solved:
Say I have a policy called SchoolPolicy:
class SchoolPolicy < ApplicationPolicy
attr_reader :user, :school
def initialize(user, school)
#user = user
#school = school
end
def index?
user.admin?
end
def show?
user.admin?
end
def new
create?
end
def edit
update?
end
def create
user.admin?
end
def update?
user.admin?
end
def destroy?
user.admin?
end
end
Then in my SchoolsController, I will have the following:
class Api::V1::SchoolsController < ApplicationController
before_action :set_school, only: [:show, :update, :destroy]
after_action :verify_authorized, except: :show
# GET /schools
def index
#schools = School.all
authorize #schools
render json: SchoolSerializer.new(#schools).serializable_hash.to_json
end
# GET /schools/1
def show
render json: SchoolSerializer.new(#school).serializable_hash.to_json
end
# POST /schools
def create
#school = School.new(school_params)
authorize #school
if #school.save
render json: SchoolSerializer.new(#school).serializable_hash.to_json, status: :created, location: api_v1_school_url(#school)
else
render json: #school.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /schools/1
def update
authorize #school
if #school.update(school_params)
render json: SchoolSerializer.new(#school).serializable_hash.to_json
else
render json: #school.errors, status: :unprocessable_entity
end
end
# DELETE /schools/1
def destroy
authorize #school
#school.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_school
#school = School.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def school_params
params.require(:school).permit(:name, :alias, :code)
end
end
Note:
I used an after_action callback to call the verify_authorized method to enforce authorization for the controller actions
I did not call the authorize method on the show action because it was skipped for authorization by me out of choice based on my design.
The instance variables called by the authorize method corresponds to the instance variable of the controller actions being called. So for the index action it is #schools and for the create action it is #school and so on.
That's all.
I hope this helps