I'm building the token authentication for the rails side of the project project. which uses devise and JWT gems. I need to write a method(in session controller) to destroy the user session. Does anyone know how to go about doing this? in the front end the token is held in sessions when the user is logged in.
class SessionsController < Devise::SessionsController
# protect_from_forgery with: :null_session, if: ->{request.format.json?}
# skip_before_action :verify_authenticity_token
def create
user = User.find_by_email(params[:email])
if user && user.valid_password?(params[:password])
#current_user = user
else
render json: { errors: { 'email or password' => ['is invalid'] } }, status: :unprocessable_entity
end
end
def destroy
# stuck here
end
end
here's the application controller too
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
respond_to :json
before_action :underscore_params!
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user
private
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
end
def authenticate_user
if request.headers['Authorization'].present?
authenticate_or_request_with_http_token do |token|
begin
jwt_payload = JWT.decode(token, Rails.application.secrets.secret_key_base).first
#current_user_id = jwt_payload['id']
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
head :unauthorized
end
end
end
end
def underscore_params!
params.deep_transform_keys!(&:underscore)
end
def authenticate_user!(options = {})
head :unauthorized unless signed_in?
end
def current_user
#current_user ||= super || User.find(#current_user_id)
end
def signed_in?
#current_user_id.present?
end
end
Related
I have a Ruby on Rails backend, currently using cookies & sessions to authenticate and login users. I believe session's default end time is when the user closes their browser, but I want the session to end 1 hour after being created.
Below are the controllers used to create the session and authenticate users:
sessions_controller.rb
class SessionsController < ApplicationController
skip_before_action :authorize, only: [:create]
include ::ActionController::Cookies
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
render json: user
else
render json: {errors: "check email and password"}, status: :unauthorized
end
end
def destroy
session.delete :user_id
head :no_content
end
end
users_controller.rb
class UsersController < ApplicationController
skip_before_action :authorize, only: [:create]
def show
render json: #current_user
end
end
application_controller.rb
class ApplicationController < ActionController::API
include ActionController::Cookies
before_action :authorize
private
def authorize
#current_user ||= User.find_by(id: session[:user_id])
render json: {errors: "Not authorized"}, status: :unauthorized unless #current_user
end
end
You can set the expiry time in an intializer like so:
Rails.application.config.session_store :cookie_store, key: '_your_custom_session_key', expire_after: 1.hour.to_i
This will make sure your sessions expire after 1 hour. You can try with a smaller time frame like 1.minute to verify.
I recently created an errors_controller.rb file which basically will route all non-existing page even those unathorized page by non-admin users to a 404 page:
class ErrorsController < ApplicationController
def page_not_found
show_404
end
end
And then on my routes.rb file:
match '*path', via: :all, to: 'errors#page_not_found'
I am using Pundit gem for my authorization and here's my users policy:
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def index?
user.admin?
end
end
Now the problem is whenever I visits a page that is non existing it shows me this:
But if a non-admin user visit a page that is unathorized to access (for instance localhost:3000/users-list), it's showing this:
How do I make sure to show the 404 page or a specific page on my public folder whenever an unathorized user visits a page that is can only access by an admin?
UPDATE: Here's the content of my application_controller.rb file:
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
rescue_from ActiveRecord::RecordNotFound, with: :show_404
def after_sign_in_path_for(resource)
stored_location_for(resource) || contacts_path
end
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:user_avatar, :name, :email, :password, :password_confirmation)}
devise_parameter_sanitizer.permit(:account_update) { |u| u.permit(:user_avatar, :name, :email, :password, :current_password)}
end
private
def user_not_authorized
flash[:danger] = "You are not authorized to perform this action."
redirect_to action: :index
end
def show_404
render template: "errors/404", status: 404
end
end
You should rewrite user_not_authorized on controller, that you call when a non-admin user visit a page that is unathorized to access
def user_not_authorized
show_404
end
I want to redirect back to current page after sign in I have devise already in my rails application. I have followed this tutorial of devise https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update#storelocation-to-the-rescue.
What is the problem after implementing this is explained below:
I enter URL directly without logging in /tasks/1 at this point this url get saved in session[:user_return_to]
then I am redirected to /users/sign_in at this point value in session[:user_return_to] gets nil because of this I cant redirect to previous page after logging in the application.
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :store_user_location!, if: :storable_location?
before_action :authenticate_user!
before_filter :configure_permitted_parameters, if: :devise_controller?
before_filter :prepare_exception_notifier
rescue_from ActionController::RoutingError, with: :controller_error
rescue_from ActiveRecord::RecordNotFound, with: :active_record_error
rescue_from CanCan::AccessDenied do |exception|
render file: "#{Rails.root}/app/views/pages/not_authorized.html.erb", status: 403
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:role_id, :username, :first_name, :last_name, :address, :organization_name , :hours_in_day, :organization_id, :job_title])
devise_parameter_sanitizer.permit(:account_update, keys: [:role_id, :username, :first_name, :last_name,:organization_id, :division_id, :department_id, :email, :password, :password_confirmation, :current_password, :job_title, :avatar, :building_id, :location_id])
end
private
def storable_location?
request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
end
def store_user_location!
store_location_for(:user, request.fullpath)
end
def after_sign_in_path_for(resource)
stored_location_for(resource) || root_path
end
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
end
def prepare_exception_notifier
request.env["exception_notifier.exception_data"] = {
current_user: current_user
}
end
def active_record_error(exception)
respond_to do |f|
f.html{ render file: "errors/404", status: 404 }
f.html{ render file: "errors/500", status: 500 }
f.js{ render partial: "errors/ajax_404", status: 404 }
f.js{ render partial: "errors/ajax_500", status: 500 }
end
end
end
Try the following in the application_controller
class ApplicationController < ActionController::Base
after_filter :store_location
before_action :authenticate_user!
private
def store_location
# store last url as long as it isn't a /users path
session[:previous_url] = request.fullpath unless request.fullpath =~ /\/users/
end
def after_sign_in_path_for(resource)
session[:previous_url] || root_path
end
end
it's working on my hand
I have two layouts Admin and Domain. And I don't need any extra configuration in Admin layout. but if user tries to access Domain layout they must be in their valid domain.
This means that, I need to customize all of my Domain policy to include both current_user as well as current_domain. I found this can be done with UserContext and pundit_user... so here is what I have done:
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def pundit_user
UserContext.new(current_user, current_domain)
end
def after_sign_out_path_for(resource)
root_path
end
def current_domain
#current_domain ||= Domain.where(name: requested_domain).first
end
helper_method :current_domain
private
def requested_domain
return request.env["SERVER_NAME"]
end
def user_not_authorized
# reset_session
flash[:alert] = "You are not authorized to perform this action"
redirect_to(request.referrer || root_path)
end
end
Note that, when I access Admin layout, current_domain will be nil and if I visit any routes of Domain layout, then current_domain will set to currently accessing domain.
user_context.rb
class UserContext
attr_reader :current_user, :current_domain
def initialize(current_user, current_domain)
#current_user = current_user
#current_domain = current_domain
end
end
PROBLEM
Suppose I have this policy:
user_policy.rb
class UserPolicy < ApplicationPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def index?
binding.pry # debugging
current_user.admin? ||
current_user.domain == current_domain
end
private
def current_user
# return user.is_a?(User) ? user : user.current_user
user.current_user
end
def current_domain
# return user.is_a?(User) ? nil : user.current_domain
user.current_domain
end
end
when application runs current_user and current_domain must available in UserPolicy as per documentation(https://github.com/elabs/pundit#additional-context).
But I am getting
undefined method `current_user' for #<User:0x007fcefbc2b150>
That means, still I have user object in it, not user.current_user and user.current_domain
Please let me know, if you need further description. What am I missing here?
It was my own dumb mistake.
PROBLEM
I had a before_filter call in domain/base_controller.rb something like:
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# PROBLEM: this line was updating pundit_user again to user object
raise Pundit::NotAuthorizedError unless DomainConsolePolicy.new(current_user, current_domain).authorized?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
SOLUTION:
I have used headless policy for this because now I have both current_user and current_domain set with pundit_user in application_controller
domain/base_controller.rb
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# SOLUTION
authorize :domain_console, :has_access?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
policy/domain_console_policy.rb
class DomainConsolePolicy < Struct.new(:user, :domain_console)
def has_access?
user.current_user.admin? ||
user.current_user.domain_id == user.current_domain.id
end
end
Thanks
I want to create multi user application. Admin user can create new users. how can i do this using devise. Because when after login as admin user i want add new user devise show error that "you are already signed in". How i do this using devise.
I was able to create the Admin User and logged in
User-Controller
class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
end
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Successfully created User."
redirect_to root_path
else
render :action => 'new'
end
end
def user_params
params.require(:user).permit(:email, :username, :password, :password_confirmation,:propic)
end
end
Admin Controller
class ClientsController < ApplicationController
skip_before_filter :authenticate_user!, only: [:index, :new, :create]
def new
#client = Client.new
#client.build_owner
render layout: 'sign'
end
def index
#clients = Client.all
render layout: 'welcome'
end
def create
#client = Client.new(client_params)
if #client.valid? then
Apartment::Tenant.create(#client.subdomain)
Apartment::Tenant.switch(#client.subdomain)
#client.save
redirect_to new_user_session_url(subdomain: #client.subdomain)
else
render action: 'new'
end
end
private
def client_params
params.require(:client).permit(:name, :subdomain, owner_attributes: [:email, :username, :password, :password_confirmation,:propic])
end
end
Application Controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :load_tenant
before_filter :authenticate_user!
#rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
render 'record_not_found'
end
def load_tenant
Apartment::Tenant.switch(nil)
return unless request.subdomain.present?
client = Client.find_by(subdomain: request.subdomain)
if client then
Apartment::Tenant.switch(request.subdomain)
else
redirect_to root_url(subdomain: false)
end
end
def after_signout_path_for(resource_or_scope)
new_user_session_path
end
end
Anyone? I am super new to Ruby on Rails. All the code is the result of hefty trial and errors.