How to fix the problem, When I try authentication on local system its working perfectly, but when uploaded to heroku it comes back with error 500? - ruby

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

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

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.

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

Related

How to test HTTParty API call with Ruby and RSpec

I am using the HTTParty gem to make a call to the GitHub API to access a list of user's repos.
It is a very simple application using Sinatra that displays a user's favourite programming language based on the most common language that appears in their repos.
I am a bit stuck on how I can write an RSpec expectation that mocks out the actual API call and instead just checks that json data is being returned.
I have a mock .json file but not sure how to use it in my test.
Any ideas?
github_api.rb
require 'httparty'
class GithubApi
attr_reader :username, :data, :languages
def initialize(username)
#username = username
#response = HTTParty.get("https://api.github.com/users/#{#username}/repos")
#data = JSON.parse(#response.body)
end
end
github_api_spec.rb
require './app/models/github_api'
require 'spec_helper'
describe GithubApi do
let(:github_api) { GithubApi.new('mock_user') }
it "receives a json response" do
end
end
Rest of the files for clarity:
results.rb
require 'httparty'
require_relative 'github_api'
class Results
def initialize(github_api = Github.new(username))
#github_api = github_api
#languages = []
end
def get_languages
#github_api.data.each do |repo|
#languages << repo["language"]
end
end
def favourite_language
get_languages
#languages.group_by(&:itself).values.max_by(&:size).first
end
end
application_controller.rb
require './config/environment'
require 'sinatra/base'
require './app/models/github_api'
class ApplicationController < Sinatra::Base
configure do
enable :sessions
set :session_secret, "#3x!iltĀ£"
set :views, 'app/views'
end
get "/" do
erb :index
end
post "/user" do
#github = GithubApi.new(params[:username])
#results = Results.new(#github)
#language = #results.favourite_language
session[:language] = #language
session[:username] = params[:username]
redirect '/results'
end
get "/results" do
#language = session[:language]
#username = session[:username]
erb :results
end
run! if app_file == $0
end
There are multiple ways you could approach this problem.
You could, as #anil suggested, use a library like webmock to mock the underlying HTTP call. You could also do something similar with VCR (https://github.com/vcr/vcr) which records the results of an actual call to the HTTP endpoint and plays back that response on subsequent requests.
But, given your question, I don't see why you couldn't just use an Rspec double. I'll show you how below. But, first, it would be a bit easier to test the code if it were not all in the constructor.
github_api.rb
require 'httparty'
class GithubApi
attr_reader :username
def initialize(username)
#username = username
end
def favorite_language
# method to calculate which language is used most by username
end
def languages
# method to grab languages from repos
end
def repos
repos ||= do
response = HTTParty.get("https://api.github.com/users/#{username}/repos")
JSON.parse(response.body)
end
end
end
Note that you do not need to reference the #username variable in the url because you have an attr_reader.
github_api_spec.rb
require './app/models/github_api'
require 'spec_helper'
describe GithubApi do
subject(:api) { described_class.new(username) }
let(:username) { 'username' }
describe '#repos' do
let(:github_url) { "https://api.github.com/users/#{username}/repos" }
let(:github_response) { instance_double(HTTParty::Response, body: github_response_body) }
let(:github_response_body) { 'response_body' }
before do
allow(HTTParty).to receive(:get).and_return(github_response)
allow(JSON).to receive(:parse)
api.repos
end
it 'fetches the repos from Github api' do
expect(HTTParty).to have_received(:get).with(github_url)
end
it 'parses the Github response' do
expect(JSON).to have_received(:parse).with(github_response_body)
end
end
end
Note that there is no need to actually load or parse any real JSON. What we're testing here is that we made the correct HTTP call and that we called JSON.parse on the response. Once you start testing the languages method you'd need to actually load and parse your test file, like this:
let(:parsed_response) { JSON.parse(File.read('path/to/test/file.json')) }
You can mock those API calls using https://github.com/bblimke/webmock and send back mock.json using webmock. This post, https://robots.thoughtbot.com/how-to-stub-external-services-in-tests walks you through the setup of webmock with RSpec (the tests in the post mock GitHub API call too)

Facebook Graph API for websites using Ruby Koala gem in Sinatra

I want to implement Facebook login for web apps. All I need is the basic public information of a user for the account creation steps.
This is what I have done:
Created a basic Facebook app with nearly no custom permissions.
Used the APP_ID and APP_SECRET in Koala to get access_token.
Everything worked perfectly, I am able to login/logout.
Just that the only information I am able to get back when I do: graph.get_object('me') is the logged in user's name and an id (It doesn't look like the default Facebook id).
Surprised whether something changed in the new API, I tested the gem in the console using the access_token from graph explorer (where all permissions are enabled by default). And I get all data using the same method call.
When I review what all the app gets while signing up; I see that the user's basic information, profile pic and other public data will be accessible to the app.
Any idea why this is so? It seems I am missing something obvious. The code is available in Github. But this is pretty much everything to it:
require 'bundler'
Bundler.require :default
Dotenv.load '.env'
require_relative './app/constants.rb'
module Banana
class App < Sinatra::Base
use Rack::Session::Cookie, secret: COOKIE_SECRET
set :public_folder, File.dirname(__FILE__) + '/bower_components'
get '/' do
if logged_in?
haml :welcome_in, layout: :layout
else
haml :log_in, layout: :layout
end
end
get '/log_out' do
session['oauth'] = nil
session['access_token'] = nil
redirect '/'
end
get '/log_in' do
session['oauth'] = Koala::Facebook::OAuth.new(APP_ID, APP_SECRET, "#{request.base_url}/call_back")
redirect session['oauth'].url_for_oauth_code()
end
get '/call_back' do
begin
session['access_token'] = session['oauth'].get_access_token(params[:code])
rescue
redirect '/?error=user_denied'
end
redirect '/'
end
get '/test' do
if logged_in?
p graph.get_object("rakeshbs")
"e"
else
redirect '/'
end
end
def logged_in?
!session['access_token'].nil?
end
def toggle_access
logged_in? ? '/log_out' : '/log_in'
end
def graph
#graph ||= Koala::Facebook::API.new(session['access_token'])
end
def errored?
!params["error"].nil?
end
def user
p graph.get_connections(:me, :photos) # This is just nil
#user ||= OpenStruct.new(
name: graph.get_object("me")["name"], # All I get here is just a hash with the name and an id!
photo: 'http://semantic-ui.com/images/avatar/small/elliot.jpg'
)
end
end
end
You should add fields parameter.
Something like this:
graph.get_object('me', { fields: 'id,first_name,last_name,gender,birthday,photos,email' })

testing padrino post methods are stopped by csrf

I have a padrino controller with a single post method and a single get method. I can use rack-test to test the get method but not the post method. When I am testing the request returns 403. I think this is because of padrino's built in csrf protection because when I comment out the line with set :protect_from_csrf, true I can test the post route. Obviously I don't want to comment out this line as csrf is useful. How can I get temporary access to test these routes for the purpose of testing?
Controller
SailPowerCourses::Admin.controllers :owners do
get :index do
puts 'hello'
end
post :index do
puts params
end
end
Test
class OwnersControllerTest < MiniTest::Test
def setup
app SailPowerCourses::Admin
end
def test_creates_an_owner
email = 'test#example.com'
assert_empty Owner
post '/owners', owner: {email: email}
puts last_response.status
refute_empty Owner
end
def test_other
email = 'test#example.com'
get '/owners', owner: {email: email}
end
end
```
When setting up an app in minitest you can use a block to access and change settings. such as csrf protection. I found the best solution to be the following. in test_config.rb I set up a version of the app with csrf protection off.
class OwnersControllerTest < MiniTest::Test
def setup
app SailPowerCourses::Admin do
set :protect_from_csrf, false
end
end
def test_creates_an_owner
email = 'test#example.com'
assert_empty Owner
post '/owners', owner: {email: email}
puts last_response.status
refute_empty Owner
end
def test_other
email = 'test#example.com'
get '/owners', owner: {email: email}
end
end

Devise not storing sessions and losing credentials after redirect

It is a VERY strange bug and I am leading with it for 24 hours. It was working well and suddenly it started to fail.
The problem:
When I want to login with Facebook, the app redirec to Facebook permissions request, go back, save the update in the account model (access_token, and updated_at), but I am redirected to the home without permissions to access to signed_in sections.
My stack is:
Rails4, Devise 3.0.0.rc, Omniauth, Omniauth-facebook 1.4.0.
The app only accept login with Facebook.
Take a look:
Omniauth controller: account_signed_in? = true
class Accounts::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#account = Account.find_for_facebook_oauth(request.env["omniauth.auth"], current_account)
if #account.persisted?
sign_in_and_redirect #account, :event => :authentication #this will throw if #user is not activated
puts account_signed_in? # <-- true
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_account_registration_url
end
end
ApplicationController: account_signed_in? = true
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
private
def stored_location_for(resource_or_scope)
nil
end
def after_sign_in_path_for(resource_or_scope)
puts account_signed_in? # <-- true
current_account.pages.empty? ? new_page_path : pages_path
end
StaticController (home) account_signed_in? = false
class StaticController < ApplicationController
def home
puts account_signed_in? # <- false
render layout: 'home'
end
I don't know if can there be something that disturb the normal flow of sessions between Devise and Rails.
Found that!
The sessions weren't saved because of the domain parameter in session_store.rb:
BrainedPage::Application.config.session_store :cookie_store,
key: '_my_session', :domain => Rails.configuration.domain
Seems I had changed the domain configuration in development environment (added port, because I was using this var for other propose too), and I didn't realize the impact it could make.

How do I use omniauth in rspec for sinatra?

Shortened version:
Using the omniauth gem for sinatra, I can't get rspec log in to work and keep my session for subsequent requests.
Based on suggestions from http://benprew.posterous.com/testing-sessions-with-sinatra, and turning off sessions, I've isolated the problem to this:
app.send(:set, :sessions, false) # From http://benprew.posterous.com/testing-sessions-with-sinatra
get '/auth/google_oauth2/callback', nil, {"omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2] }
# last_request.session => {"uid"=>"222222222222222222222", :flash=>{:success=>"Welcome"}}
# last_response.body => ""
follow_redirect!
# last_request.session => {:flash=>{}}
# last_response.body => Html for the homepage, which is what I want
How do I get rspec to follow the redirect and retain the session variables? Is this possible in Sinatra?
From http://benprew.posterous.com/testing-sessions-with-sinatra, it seems like I'd have to send the session variables on each get/post request that I require login for, but this wouldn't work in the case of redirects.
The details:
I'm trying to use the omniauth gem in sinatra with the following setup:
spec_helper.rb
ENV['RACK_ENV'] = 'test'
# Include web.rb file
require_relative '../web'
# Include factories.rb file
require_relative '../test/factories.rb'
require 'rspec'
require 'rack/test'
require 'factory_girl'
require 'ruby-debug'
# Include Rack::Test in all rspec tests
RSpec.configure do |conf|
conf.include Rack::Test::Methods
conf.mock_with :rspec
end
web_spec.rb
describe "Authentication:" do
before do
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:google_oauth2, {
:uid => '222222222222222222222',
:info => {
:email => "someone#example.com",
:name => 'Someone'
}
})
end
describe "Logging in as a new user" do
it "should work" do
get '/auth/google_oauth2/'
last_response.body.should include("Welcome")
end
end
end
When trying to authenticate, I get a <h1>Not Found</h1> response. What am I missing?
On the Integration testing page of the omniauth docs, it mentions adding two environment variables:
before do
request.env["devise.mapping"] = Devise.mappings[:user]
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
But seems to be for rails only, as I added
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
to my before block in my spec and I get this error:
Failure/Error: request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
ArgumentError:
wrong number of arguments (0 for 1)
Edit:
Calling get with
get '/auth/google_oauth2/', nil, {"omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2]}
seems to give me last_request.env["omniauth.auth"] equal to
{"provider"=>"google_oauth2", "uid"=>"222222222222222222222", "info"=>{"email"=>"someone#example.com", "name"=>"Someone"}}
which seems right, but last_response.body still returns
<h1>Not Found</h1>
A partial answer...
The callback url works better, with the added request environment variables:
get '/auth/google_oauth2/callback', nil, {"omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2]}
follow_redirect!
last_response.body.should include("Welcome")
However, this doesn't work with sessions after the redirect, which is required for my app to know someone is logged in. Updated the question to reflect this.
Using this gist (originating from https://stackoverflow.com/a/3892401/111884) to store session data, I got my tests to store the session, allowing me to pass the session to further requests.
There might be an easier way though.
Setup code:
# Omniauth settings
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:google_oauth2, {
:uid => '222222222222222222222',
:info => {
:email => "someone#example.com",
:name => 'Someone'
}
})
# Based on https://gist.github.com/375973 (from https://stackoverflow.com/a/3892401/111884)
class SessionData
def initialize(cookies)
#cookies = cookies
#data = cookies['rack.session']
if #data
#data = #data.unpack("m*").first
#data = Marshal.load(#data)
else
#data = {}
end
end
def [](key)
#data[key]
end
def []=(key, value)
#data[key] = value
session_data = Marshal.dump(#data)
session_data = [session_data].pack("m*")
#cookies.merge("rack.session=#{Rack::Utils.escape(session_data)}", URI.parse("//example.org//"))
raise "session variable not set" unless #cookies['rack.session'] == session_data
end
end
def login!(session)
get '/auth/google_oauth2/callback', nil, { "omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2] }
session['uid'] = last_request.session['uid']
# Logged in user should have the same uid as login credentials
session['uid'].should == OmniAuth.config.mock_auth[:google_oauth2]['uid']
end
# Based on Rack::Test::Session::follow_redirect!
def follow_redirect_with_session_login!(session)
unless last_response.redirect?
raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
end
get(last_response["Location"], {}, { "HTTP_REFERER" => last_request.url, "rack.session" => {"uid" => session['uid']} })
end
def get_with_session_login(path)
get path, nil, {"rack.session" => {"uid" => session['uid']}}
end
Sample rspec code:
describe "Authentication:" do
def session
SessionData.new(rack_test_session.instance_variable_get(:#rack_mock_session).cookie_jar)
end
describe "Logging in as a new user" do
it "should create a new account with the user's name" do
login!(session)
last_request.session[:flash][:success].should include("Welcome")
get_with_session_login "/"
follow_redirect_with_session_login!(session)
last_response.body.should include("Someone")
end
end
end

Resources