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.
Related
I'm quite new to RoR programming and stuck while trying to migrate my authentication from Clearance to JWT. I created all of the required methods in ApplicationController and UsersController and even managed to sign up a user, save the user's password_digest to the database and then log in a user (in terms of POST params, I mean that no errors were thrown). However, I fail to keep the user logged in. I understand that there should be an auth_header attached to each request by my user, but how do I create one? I googled it multiple times, but failed to find how to handle it in terms of front-end. Everybody seems to use these fancy apps with buttons to create all the required headers and send raw json data ((
In other words, I have my JWT token encoded in the entrance method (as posted below) but I cannot understand how to pass it as a header in each and every request to the app further on?
users_controller.rb
class UsersController < ApplicationController
def create
#user = User.create(user_params)
if #user.valid?
token = encode_token({ user_id: #user.id })
redirect_to root_path
else
render json: { error: "Invalid email or password"}, status: :unprocessable_entity
end
end
def entrance
#user = User.find_by(email: user_params[:email])
if #user && #user.authenticate(user_params[:password])
token = encode_token({ user_id: #user.id })
redirect_to root_path
else
render json: { error: "Invalid email or password"}, status: :unprocessable_entity
end
end
def login
render 'login'
end
def signup
#user = User.new
render 'signup'
end
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user
def encode_token(payload)
JWT.encode(payload, 'secret')
end
def decode_token
auth_header = request.headers["Authorization"]
if auth_header
token = auth_header.split(' ')[1]
begin
JWT.decode(token, 'secret', true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def current_user
decoded_token = decode_token()
if decoded_token
user_id = decoded_token[0]['user_id']
#user = User.find(user_id)
end
end
def signed_in?
current_user.present?
end
def authorize
if signed_in?
redirect_to root_path
else
redirect_to "/log_in"
end
end
I truncated the code a bit, but all the relevant methods are included.
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 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
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 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.