Unable to authenticate JWT in Ruby on Rails with Devise - ruby-on-rails

I'm currently having trouble authenticating my Jason Web Token in Ruby on Rails 5.0.1. I'm using devise and jwt gems to validate the jwt generated for the user during log-in.
SessionController
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
jwt_create
end
def jwt_create
user = User.find_by(email: params[:user][:email])
if user.valid_password? params[:user][:password]
token = JsonWebToken.encode(sub: user.id)
flash[:notice] = "JWT Key: #{token}"
end
end
I'm currently using this to encode and decode my payload.
class JsonWebToken
def self.encode(payload)
payload['exp'] = 2.hours.from_now.to_i
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def self.decode(token)
JWT.decode(token, Rails.application.secrets.secret_key_base)
end
end
And authenticating as a before_action with the following:
protected
def authenticate_request!
unless user_id_in_token?
render json: { errors: ['Not Authenticated'] }, status: :unauthorized
return
end
#current_user = User.find(auth_token[:user_id])
rescue JWT::VerificationError, JWT::DecodeError
render json: { errors: ['Not Authenticated'] }, status: :unauthorized
end
private
def http_token
#http_token ||= if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
end
end
def auth_token
#auth_token ||= JsonWebToken.decode(http_token)
end
def user_id_in_token?
http_token && auth_token && auth_token[:user_id].to_i
end
end
I'm not sure if my logic is flawed or I'm missing a key part to validate JWT. Any help would be greatly appreciated.

Related

How can I set auth header with JWT in Rails?

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.

Rails API not able to destroy session

I am using Rails API and devise for authentication but when sending a request to destroy the session, it doesn't return any response other then status 200.It returns the same response no matter if the user exists, is logged in or the password is incorrect.
url http://localhost:3000/users/sign_in
I'm sending a DELETE request through Postman and it just returns a 200 response. It should be returning the data which is stored in the #message
{
"user":{
"email":"abc#abc.com",
"password":"abc#abc.com"
}
}
I even tried to destory the session using the session id:
curl -X DELETE -d "3" http://localhost:3000/users/sign_out
sessions_controller.rb
class API::V1::SessionsController < Devise::SessionsController
def create
#user = User.find_by_email(user_params[:email])
if #user && #user.valid_password?(user_params[:password])
session[:user_id]=#user.id
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"
session.delete(:user_id)
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
routes.rb
devise_for :users, controllers: { registrations: 'api/v1/registrations', sessions:'api/v1/sessions'}

Rails API Forgot Password not Rendering View

so i'm building a forgot password in my Rails API, i was able to send the reset password email, but when i go to the link, it gives me 204 No Content.
I'm using lvh.me to test my api subdomain in development
This is my passwords_controller.rb
class User::PasswordsController < Devise::PasswordsController
def create
puts params
self.resource = resource_class.send_reset_password_instructions(params)
if successfully_sent?(resource)
render json: {status: 'ok'}, status: :ok, location: after_sending_reset_password_instructions_path_for(resource_name)
else
render json: {error: ['Error occurred']}, status: :internal_server_error
end
end
# GET /user/password/edit?reset_password_token=blablabla
def edit
self.resource = resource_class.new
set_minimum_password_length
resource.reset_password_token = params[:reset_password_token]
end
# PUT /user/password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
if Devise.sign_in_after_reset_password
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message!(:notice, flash_message)
resource.after_database_authentication
sign_in(resource_name, resource)
else
set_flash_message!(:notice, :updated_not_active)
end
respond_with resource, location: after_resetting_password_path_for(resource)
else
set_minimum_password_length
respond_with resource
end
end
protected
def after_resetting_password_path_for(resource)
Devise.sign_in_after_reset_password ? after_sign_in_path_for(resource) : new_session_path(resource_name)
end
# The path used after sending reset password instructions
def after_sending_reset_password_instructions_path_for(resource_name)
new_session_path(resource_name) if is_navigational_format?
puts new_session_path(resource_name) if is_navigational_format?
end
# Check if a reset_password_token is provided in the request
def assert_reset_token_passed
if params[:reset_password_token].blank?
set_flash_message(:alert, :no_token)
redirect_to new_session_path(resource_name)
end
end
# Check if proper Lockable module methods are present & unlock strategy
# allows to unlock resource on password reset
def unlockable?(resource)
resource.respond_to?(:unlock_access!) &&
resource.respond_to?(:unlock_strategy_enabled?) &&
resource.unlock_strategy_enabled?(:email)
end
def translation_scope
'devise.passwords'
end
end
The generated link looks like this: http://api.lvh.me:3000/user/password/edit?reset_password_token=blablabla
My controllers and views structure is:
controllers/user/passwords_controller.rb
views/user/passwords/edit.html.erb
My routes.rb for user looks like this:
constraints subdomain: 'api' do
scope module: 'user' do
devise_for :users,
path: '/user',
path_names: {
registration: 'signup',
sign_in: 'login',
sign_out: 'logout'
},
controllers: {
sessions: 'user/sessions',
registrations: 'user/registrations',
passwords: 'user/passwords'
}
end
end
Figured out that i had
class ApplicationController < ActionController::API which was keeping me from rendering Views, simply changing to
class ApplicationController < ActionController::Base resolved the issue

Rails 5 Devise: Return JSON On sign_in Failure

Everything I seem to find on this issue is out of date and/or doesn't work.
GOAL: When a user tries to sign in via JSON from a mobile application and the username or password is wrong, I would like Rails to return JSON data so the errors can be displayed on the app.
So far I have done the following:
class Users::SessionsController < Devise::SessionsController
# before_action :configure_sign_in_params, only: [:create]
skip_before_action :verify_authenticity_token
respond_to :json
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, :location => after_sign_in_path_for(resource) do |format|
format.json {render :json => resource } # this code will get executed for json request
end
end
end
This works well on success, but I'm not sure what to do when it fails. Right now it returns an undefined method error:
undefined method `users_url' for #<Users::SessionsController:0x0000000195fa28>
You should take a look at this SO question that will point you in the right direction.
Basically you need to handle the login failure. You could do this:
class CustomFailure < Devise::FailureApp
def respond
if http_auth?
http_auth
elsif request.content_type == "application/json"
self.status = 401
self.content_type = "application/json"
self.response_body = {success: false, error: "Unauthorized"}.to_json
else
redirect
end
end
end
This handles JSON requests. I hope this helps!
Figured it out. CustomFailure never seemed to work, no matter how many things I tried. I was able to do this within the SessionsController via handle_failed_login:
class Users::SessionsController < Devise::SessionsController
# before_action :configure_sign_in_params, only: [:create]
after_filter :handle_failed_login, :only => :new
skip_before_action :verify_authenticity_token
respond_to :json
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, :location => after_sign_in_path_for(resource) do |format|
format.json {render :json => resource } # this code will get executed for json request
end
end
private
def handle_failed_login
if failed_login?
render json: { success: false, errors: ["Login Credentials Failed"] }, status: 401
end
end
def failed_login?
(options = env["warden.options"]) && options[:action] == "unauthenticated"
end
end

Devise: Run code after confirmation

I have a bunch of code that hands over stuff from a logged in user's guest/lazy registered account to his new account which I run when a new session is created.
class SessionsController < Devise::SessionsController
def new
super
end
def create
super
logging_in # this is the method which will run
end
def destroy
super
end
end
It works when the user logs in. However when Devise logs a user in after confirmation, the above does not get run. Where should I put the method if I want it to run after a user logs in? whether by logging in or confirmation.
Thanks nash. Here's how I did it.
class ConfirmationsController < Devise::ConfirmationsController
def new
super
end
def create
super
end
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_navigational_format?
sign_in(resource_name, resource)
logging_in # Here it is
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
else
respond_with_navigational(resource.errors, :status => :unprocessable_entity){ render :new }
end
end
protected
def after_resending_confirmation_instructions_path_for(resource_name)
new_session_path(resource_name)
end
def after_confirmation_path_for(resource_name, resource)
after_sign_in_path_for(resource)
end
end
It needs to be added after sign_in because my logging_in method uses current_user.

Resources