Unable to authenticate user using devise on fields other than email - ruby-on-rails

I am unable to aunthenticate user using his cell number.
All I want is, if a user is authenticated, a devise-jwt token will be send back to him.
Current, I am getting just getting error even if the cell is correct:
Started POST "/api/v1/users/sign_in" for ::1 at 2023-02-02 03:06:46 +0500
Processing by Api::V1::SessionsController#create as JSON
Parameters: {"users"=>{"cell"=>"4151511717"}}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms | Allocations: 403)
routes.rb
namespace :api, defaults: { format: :json } do
namespace :v1 do
devise_for :users, controllers: {
sessions: 'api/v1/sessions',
}
end
end
sessions_controller.rb
class Api::V1::SessionsController < Devise::SessionsController
respond_to :json
private
def respond_with(resource, _opts = {})
render json: { message: 'Logged.' }, status: :ok
end
def respond_to_on_destroy
current_user ? log_out_success : log_out_failure
end
def log_out_success
render json: { message: "Logged out." }, status: :ok
end
def log_out_failure
render json: { message: "Logged out failure." }, status: :unauthorized
end
end
devise.rb file
Devise.setup do |config|
#............
config.secret_key = ENV['DEVISE_SECRET_KEY']
config.authentication_keys = [:cell]
config.case_insensitive_keys = [:cell]
config.strip_whitespace_keys = [:cell]
config.jwt do |jwt|
jwt.secret = ENV['DEVISE_JWT_SECRET_KEY']
jwt.expiration_time = 1.day.to_i
end
What else I am missing? Do I need to override create method?
If yes, how? As resource seems nil if I go for that method.

Related

401 Unauthorized when making request to rails api backend hosted on heroku but works on localhost

I am facing a problem where all requests that I make to my backend that requires "before_action :authenticate_request" fails. However, all the requests works on localhost. I am testing my API using insomnia. I have attached the logs below. I have checked out many posts on StackOverflow and other google resources but none of it has worked. I would appreciate any help. Do let me know if I have missed out any important info. I am also using the JWT gem
2021-06-22T08:38:07.272841+00:00 heroku[router]: at=info method=GET path="/users" host=tranquil-fjord-90719.herokuapp.com request_id=3e1fbe4f-d487-49ed-9af0-75e451f1d094 fwd="49.245.87.141" dyno=web.1 connect=0ms service=4ms status=401 bytes=494 protocol=https
2021-06-22T08:38:07.270478+00:00 app[web.1]: I, [2021-06-22T08:38:07.270376 #4] INFO -- : [3e1fbe4f-d487-49ed-9af0-75e451f1d094] Started GET "/users" for 49.245.87.141 at 2021-06-22 08:38:07 +0000
2021-06-22T08:38:07.271270+00:00 app[web.1]: I, [2021-06-22T08:38:07.271195 #4] INFO -- : [3e1fbe4f-d487-49ed-9af0-75e451f1d094] Processing by UsersController#index as */*
2021-06-22T08:38:07.272016+00:00 app[web.1]: I, [2021-06-22T08:38:07.271940 #4] INFO -- : [3e1fbe4f-d487-49ed-9af0-75e451f1d094] Filter chain halted as :authenticate_request rendered or redirected
2021-06-22T08:38:07.272206+00:00 app[web.1]: I, [2021-06-22T08:38:07.272134 #4] INFO -- : [3e1fbe4f-d487-49ed-9af0-75e451f1d094] Completed 401 Unauthorized in 1ms (Views: 0.3ms | Allocations: 154)
application_controller.rb
class ApplicationController < ActionController::API
before_action :authenticate_request
skip_before_action :authenticate_request, :only => [:index]
attr_reader :current_user
def index
render html: '<h1>NUSPM-api</h1>'.html_safe
end
private
def authenticate_request
#current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
authentication_controller.rb
class AuthenticationController < ApplicationController
skip_before_action :authenticate_request
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result, user: UserRepresenter.new(User.find_by_email(params[:email])).as_json }
else
render json: { error: command.errors }, status: :unauthorized
end
end
end
users_controller.rb
class UsersController < ApplicationController
skip_before_action :authenticate_request, only: [:create]
before_action :user, only: [:show, :update, :destroy]
before_action :authenticate_request, only: [:index, :show, :update, :destroy]
def index
#users = User.all
render json: UsersRepresenter.new(#users).as_json
end
def show
render json: UserRepresenter.new(#user).as_json
end
def create
#user = User.new(user_params)
if #user.save
token = AuthenticateUser.call(#user.email, #user.password)
render json: { message: "Account created",
token: token,
user: UserRepresenter.new(#user).as_json },
status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
end
def update
if #user.update(user_params)
render json: { message: "Account updated" }, status: :ok
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
end
def destroy
if #user
#user.destroy
render json: { message: "Account deleted" }, status: :ok
else
render json: { message: "Unable to delete account" }, status: :bad_request
end
end
private
def user
#user = User.find(params[:id])
end
def user_params
params.permit(:email, :password, :password_confirmation)
end
end
authenticate_user.rb
class AuthenticateUser
prepend SimpleCommand
def initialize(email, password)
#email = email
#password = password
end
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
attr_accessor :email, :password
def user
user = User.find_by_email(email)
return user if user && user.authenticate(password)
errors.add :user_authentication, 'invalid credentials'
nil
end
end
authorize_api_request.rb
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
#headers = headers
end
def call
user
end
private
attr_reader :headers
def user
#user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
#user || errors.add(:token, 'Invalid token') && nil
end
def decoded_auth_token
#decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else
errors.add(:token, 'Missing token')
end
nil
end
end

How to get JWT token in header when using Rails and React

I've my Sessions controller set up as below. I'm looking for a way to grab the auth_token from command.result in the authenticate method and make it available in my headers when the user logs in so I can access it inside react to authenticate users on each request.
When I make a request to the authenticate action, I get the right response with the auth_token but can't figure out how to send this response to the header and use it there.
class Api::V1::SessionsController < ApplicationController
skip_before_action :authenticate_request
include CurrentUserConcern
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result, message: 'Login successful' }
else
render json: { error: command.errors }, status: :unauthorized
end
end
def create
user = User.find_by(email: params[:email])
.try(:authenticate, params[:password])
if user
session[:user_id] = user.id
render json: {
status: :created,
logged_in: true,
user: user,
}
else
render json: {
status: 400,
}, status: 400
end
end
def logged_in
if #current_user
render json: {
logged_in: true,
user: #current_user
}
else
render json: {
logged_in: false
}
end
end
def logout
reset_session
render json: { status: 200, logged_out: true }
end
end
Figured it out.
I set a before_action in my controller to set the #token variable. This way I can use #token in any action within the controller.
class Api::V1::SessionsController < ApplicationController
skip_before_action :authenticate_request
before_action :set_token
end
Then I created a private method to set the token.
def set_token
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
#token = command.result
else
nil
end
end
And called the #token variable inside my create action passing it to set_headers.
response.set_header('token', #token)

Unable to create user object in rails backend using JWT authentication

I am trying to create User objects in a rails PostgreSQL database using JWT authentication. When I fire the create user method from my React frontend the user is not created in the backend and I suspect this has something to do with how my controllers are set up.
Here is my User controller in my Rails backend:
class Api::V1::UsersController < ApplicationController
before_action :find_user, only: [:show]
skip_before_action :authorized, only: [:index, :create]
def index
#users = User.all
render json: #users
end
def show
render json: #user
end
def create
#user = User.create(user_params)
if #user.valid?
#token = encode_token(user_id: #user.id)
render json: { user: UserSerializer.new(#user), jwt: #token }, status: :created
else
render json: {error: "Could not create user"}, status: :unprocessible_entity
end
end
end
private
def user_params
params.permit(:username, :password)
end
def find_user
#user = User.find(params[:id])
end
Here is my 'auth' controller for JWT:
class Api::V1::AuthController < ApplicationController
skip_before_action :authorized, only: [:create]
def create # POST /api/v1/login
#user = User.find_by(username: user_login_params[:username])
if #user && #user.authenticate(user_login_params[:password])
#token = encode_token({ user_id: #user.id })
render json: { user: UserSerializer.new(#user), jwt: #token }, status: :accepted
else
render json: { message: 'Invalid username or password' }, status: :unauthorized
end
end
private
def user_login_params
params.require(:user).permit(:username, :password)
end
end
And my application controller (I suspect the issue is here and has to do with the way in which I am encoding and decoding the tokens):
class ApplicationController < ActionController::API
before_action :authorized
def encode_token(payload)
JWT.encode(payload, ENV["jwt_secret"])
end
def auth_header
request.headers['Authorization']
end
def decoded_token
if auth_header()
token = auth_header.split(' ')[1]
begin
JWT.decode(token, ENV["jwt_secret"], true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def current_user
if decoded_token()
user_id = decoded_token[0]['user_id']
#user = User.find_by(id: user_id)
else
nil
end
end
def logged_in?
!!current_user
end
def authorized
render json: { message: 'Please log in' }, status: :unauthorized unless logged_in?
end
end
From my front-end I am using a fetch method to POST to the /users/ endpoint like so:
export const signupUser = (username, password) => {
return(dispatch) => {
const data = {user: {username, password} }
fetch('http://localhost:3000/api/v1/users',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify(data)
})
.then(r=>r.json())
.then(r=>{
localStorage.setItem('jwt', r.jwt)
dispatch({
type: SET_CURRENT_USER,
payload: r.user
})
})
}
}
Apologies that this is long-winded. I wanted to include all the code necessary to figure this out. Any assistance would be appreciated.
I noticed you are not saving your user object when you using a function to create user. Please check app/controllers/api/v1/users_controller.rb and modify following with valid to save method
def create
#user = User.create(user_params)
if #user.valid?
#token = encode_token(user_id: #user.id)
render json: { user: UserSerializer.new(#user), jwt: #token }, status: :created
else
render json: {error: "Could not create user"}, status: :unprocessible_entity
end
end
Change the line #user.valid? to #user.save
def create
#user = User.create(user_params)
if #user.save
#token = encode_token(user_id: #user.id)
render json: { user: UserSerializer.new(#user), jwt: #token }, status: :created
else
render json: {error: "Could not create user"}, status: :unprocessible_entity
end
end
Please note .valid? is used to validate and check if any error, and .save is used to save it. You are just checking validation and not actually saving, validation happen automatically so there is no need for this method here.

Always get JWTSessions::Errors::Unauthorized in Api::V1, nil JSON Token in Rails and jwt_sessions

I'm new to JWT authorization and have followed the boiler plate to set up auth in Rails. When I try to test my routes by calling localhost:5000/api/v1/users or any controller for that matter, I get the following everytime:
JWTSessions::Errors::Unauthorized in Api::V1
Nil JSON web token
Obviously a token isn't being generated but not sure how to create one from just checking controllers in Rails. Is there a way to do this with Postman?
Here are my relevant controllers:
Application Controller:
class ApplicationController < ActionController::API
include JWTSessions::RailsAuthorization
rescue_from JWTSessions::Errors::Unauthorized, with: :not_authorized
private
def current_user
#current_user ||= User.find(payload['user_id'])
end
def not_authorized
render json: { error: 'Not authorized' }, status: :unauthorized
end
end
Signup:
class SignupController < ApplicationController
def create
user = User.new(user_params)
if user.save
payload = { user_id: user.id }
session = JWTSessions::Session.new(payload: payload, refresh_by_access_allowed: true)
tokens = session.login
response.set_cookie(JWTSessions.access_cookie,
value: tokens[:access],
httponly: true,
secure: Rails.env.production?)
render json: { csrf: tokens[:csrf] }
else
render json: { error: user.errors.full_messages.join(' ') }, status: :unprocessable_entity
end
end
private
def user_params
params.permit(:first_name, :last_name, :username, :email, :password, :password_confirmation)
end
end
UsersController:
class Api::V1::UsersController < ApplicationController
before_action :find_user, only: [:update, :show, :destroy]
def index
#users = User.all
render json: #users, status: :accepted
end
def create
#user = User.new(user_params)
if #user.save
render json: {user: UserSerializer.new(#user), token: Rails.application.credentials.jwt}, status: :ok
else
render json: {errors: #user.errors.full_messages}
end
end
def show
if #user
if curr_user.id == #user.id
render json: #user
else
render json: {errors: #user.errors.full_messages }, status: :unprocessible_entity
end
else
render json: {errors: "User not found!"}
end
end
def update
if curr_user.id == #user.id
#user.update(user_params)
if #user.save
render json: #user, status: :accepted
else
render json: { errors: #user.errors.full_messages }, status: :unprocessible_entity
end
end
end
def destroy
if curr_user.id == #user.id
#user.delete
render json: "user deleted", status: :ok
else
render json: { errors: "You are not authorized to delete"}
end
end
private
def user_params
params.permit(:first_name, :last_name, :email, :username, :password_digest)
end
def find_user
#user = User.find(params[:id])
end
end
Routes
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
namespace :api do
namespace :v1 do
resources :users, only: [:index, :show, :create, :update, :destroy]
resources :categories, only: [:index, :show, :create, :update, :destroy]
resources :opportunities
resources :opportuniy_api, only: [:index, :show]
end
end
post 'refresh', controller: :refresh, action: :create
post 'signin', controller: :signin, action: :create
post 'signup', controller: :signup, action: :create
delete 'signin', controller: :signin, action: :destroy
end
It is showing up "Nil JSON Web Token" because rails is unable to find any authorization token in request.headers["Authorization"].
Going through the jwt_sessions readme file https://github.com/tuwukee/jwt_sessions you will find that "Headers must include Authorization: Bearer with access token." This is what you are missing in your case.
Secondly, you don't need to use CSRF tokens in rails. As a Rails developer, you basically get CSRF protection for free. It starts with this single line in application_controller.rb, which enables CSRF protection:
protect_from_forgery with: :exception
I have tried the same and my code works fine:
Signup:
class SignupController < ApplicationController
skip_before_action :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
payload = { user_id: user.id }
session = JWTSessions::Session.new(payload: payload, refresh_by_access_allowed: true)
tokens = session.login
response.set_cookie(JWTSessions.access_cookie,
value: tokens[:access],
httponly: true,
secure: Rails.env.production?)
render json: tokens
else
render json: { error: user.errors.full_messages.join(' ') }, status: :unprocessable_entity
end
end
private
def user_params
params.permit(:username, :email, :password, :password_confirmation)
end
end
Or you can make it even simpler: Signup
class SignupController < ApplicationController
def create
user = User.new(user_params)
if user.save
payload = { user_id: user.id }
session = JWTSessions::Session.new(payload: payload, refresh_by_access_allowed: true)
render json: session.login //or render json: session.login[:access]
else
render json: { error: user.errors.full_messages.join(' ') }, status: :unprocessable_entity
end
end
private
def user_params
params.permit(:email, :password, :password_confirmation, :signup)
end
end
application_controller:
class ApplicationController < ActionController::Base
protect_from_forgery prepend: true, with: :exception
include JWTSessions::RailsAuthorization
rescue_from JWTSessions::Errors::Unauthorized, with: :not_authorized
def index
render template: 'application'
end
private
def current_user
#current_user ||= User.find(payload['user_id'])
end
def not_authorized
render json: { error: 'Not authorized' }, status: :unauthorized
end
end
ajax call from frontend(vue) to rails: Inspect your token and you will get the access_token within it. Or render token[:access] from rails to frontend. Include this access token in the Header for every request you make to the rails api.
$.ajax({
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + <access_token>);
},
url: '/api/home'..
Hope this helps :)
I dit it, for me it worked
def current_user
return unless request.headers.include? "Authorization"
#current_user ||= User.find(payload['user_id'])
end

Manual Update password Rails Devise

I have an API based Rails app and I need to add a changing password section for clients after login. this is y codes so far:
# routes.rb
resources :passwords, only: %i[index]
post '/passwords/update_password', to: 'passwords#update_password'
passwords_controller.rb
class Api::PasswordsController < ApplicationController
respond_to :json
before_action :auth_check
def auth_check
if !user_signed_in?
render json: {:status => false, :msg => 'Access denied!'}
end
end
def update_password
user = User.find(current_user['_id'])
password = params["password"]
if password && !password.blank?
user.password = user.password_confirmation = password
end
if user.save
render json: {company: user}, status: 200
else
render json: {message: "Problem updating company"}, status: 500
end
end
end
And this is XHR request from client-side
axios({
url: '/api/passwords/update_password',
method: 'POST',
body: {
password: password,
password_confirmation: password_confirmation
}
})
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
});
Its not working!
You should be able to use current_user. I edited the code. If it doesn't work, can you write the error here? Make sure the post request goes to update_password action.
class Api::PasswordsController < ApplicationController
respond_to :json
before_action :auth_check
def update_password
password = params.dig(:password)
password_confirmation = params.dig(:password_confirmation)
if password.present? && password == password_confirmation
if current_user.update(password: password, pasword_confirmation: password_confirmation)
render json: { company: user }, status: 200
else
render json: { message: 'Problem updating company' }, status: 500
end
end
end
private
def auth_check
render json: { status: false, msg: 'Access denied!' } unless user_signed_in?
end
end

Resources