0

I am new to rails and react, this might be a simple one but i cant seem to figure it out.

I am trying to implement a simple jwt authentication using ruby on rails with react as client. I followed the steps that was suggested in : https://www.pluralsight.com/guides/token-based-authentication-with-ruby-on-rails-5-api

It works as expected on my local system but when i uploaded my app on to heroku it always comes back with error : 500. All the other 'Post' and 'Get' requests work normally. Its only when i try to authenticate and get the auth_token back it runs into 500 error.

this is the request format post: localhost:3001/api/authenticate

and body:

{
    "email": "evin@xyz.com",
    "password": "evin"
}

I verified that this data is available on heroku by using get which works perfectly.

I have been working on resolving this for over 2 days now. There is very little information available online on this authentication. There was plenty of recommendations on using auth0. But i could not find much help with this form of authentication.

This is what i have

#Path: /app/controllers/application_controller.rb

class ApplicationController < ActionController::API
  before_action :authenticate_request
  attr_reader :current_user

  private

  def authenticate_request
    @current_user = AuthorizeApiRequest.call(request.headers).result
    render json: { error: 'Not Authorized' }, status: 401 unless @current_user
  end
end


#Path: app/controllers/api/authentication_controller.rb

class Api::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 }
   else
     render json: { error: command.errors }, status: :unauthorized
   end
 end
end



#Path: /app/commands/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


#Path:  /app/commands/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




#Path: /lib/json_web_token.rb

class JsonWebToken
 class << self
   def encode(payload, exp = 24.hours.from_now)
     payload[:exp] = exp.to_i
     JWT.encode(payload, Rails.application.secrets.secret_key_base)
   end

   def decode(token)
     body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
     HashWithIndifferentAccess.new body
   rescue
     nil
   end
 end
end





#path: /config/application.rb

require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Deveycon
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    #Autoload lib for encrypt and decrypt
    config.autoload_paths << Rails.root.join('lib')

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true
  end
end

  • There is no need to prefix your question title with "How to fix the problem" or "How to fix the error". You're asking a question on Stack Overflow. We _know_ you have a question about a problem or error. Focus on what that question is. Ideally, your title should capture your question very concisely. Please read [ask]. – ChrisGPT was on strike Jul 29 '19 at 10:53

3 Answers3

5

I had similar issues, the API works perfectly on localhost after uploading to Heroku, I still got unauthorized on secure pages even with the token on the headers. I added

production:
   secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

to config/secrets.yml

Acushla
  • 89
  • 1
  • 6
  • This works alhtough for me I'm using a different framework. But it usually has to do with the key that signs the token like this one in Rails – puerile Sep 24 '21 at 13:24
2

Please check the more details log of your heroku application by using Heroku CLI.

heroku logs -t

If the problem with AuthenticateUser::JsonWebToken use auto loaded in your

config/application.rb

class Application < Rails::Application
    #.....
    config.autoload_paths << Rails.root.join('lib')
    #.....
end

I hope that helpful to resolve your issue.

Piyush Awasthi
  • 368
  • 3
  • 13
  • i checked the log its says : NameError (uninitialized constant AuthenticateUser::JsonWebToken) . I had this error on my local environment that was because i was missing : "config.autoload_paths << Rails.root.join('lib')" in config/application.rb! once i added that it worked on local. what should i do on heroku ? – sandesh b nataraj Jul 29 '19 at 06:34
  • Yeah you can add `config.autoload_paths << Rails.root.join('lib')` or `config.eager_load_paths << Rails.root.join('lib')` in your config/application.rb. Let me update comment as well. – Piyush Awasthi Jul 29 '19 at 06:43
  • hey i added "config.eager_load_paths << Rails.root.join('lib')" which took of that problem. now i have the following error : TypeError (no implicit conversion of nil into String): lib/json_web_token.rb:5:in `encode' – sandesh b nataraj Jul 29 '19 at 07:05
  • it would of great help if u can shed some light on it !! lib/json_web_token.rb i have uploaded the code please take a look! – sandesh b nataraj Jul 29 '19 at 07:07
  • Try something like that https://gist.github.com/piyushawasthi/a7fd9a42dd863ed1b6bb0fbc92890175, If that work for you. – Piyush Awasthi Jul 29 '19 at 07:16
2

In #lib/JsonWebToken: Just increase the exp time of token and replace .secrets.secret_key_base with .credentials.read

class JsonWebToken
  class << self
    def encode(payload, exp = 1200.hours.from_now)
      payload[:exp] = exp.to_i
      JWT.encode(payload, Rails.application.credentials.read)
    end

    def decode(token)
      body = JWT.decode(token, Rails.application.credentials.read)[0]
      HashWithIndifferentAccess.new body
    rescue
    nil
    end
  end
end
Lori
  • 1,392
  • 1
  • 21
  • 29