Recently, I deployed a rails backend and a vue frontend to heroku. Everything seemed fine a few days ago, until I tried logging into the web app today. I am getting 401 responses immediately after because after you login, the app is supposed to display your comic books from database.
For background, I am following a Vue.js/Rails tutorial on youtube by webcrunch. In the tutorial, localStorage is used along with JWTSessions and axios. I used the following question, but it didn't offer the answer I was looking for HttpGet 401 status code followed by 200 status code. I also tried adding trailing slashes to my api urls, but that did not fix the issue. In the vue frontend, the tutorial also made use of a axios/index.js file that configures instances of axios requests.
application_controller.rb
class ApplicationController < ActionController::Base
include JWTSessions::RailsAuthorization
rescue_from JWTSessions::Errors::Unauthorized, with: :not_authorized
private
# access current user
def current_user
@current_user ||= User.find(payload['user_id'])
end
def not_authorized
# render actual data back since no view
render json: { error: 'Not authorized' }, status: :unauthorized
end
end
signin_controller.rb (Not including signup_controller.rb because they are similar)
class SigninController < ApplicationController
before_action :authorize_access_request!, only: [:destroy]
skip_before_action :verify_authenticity_token
def create
user = User.find_by!(email: params[:email])
if user.authenticate(params[:password])
payload = { user_id: user.id }
# allow user to log in again if failed attempt
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
not_authorized
end
end
def destroy
session = JWTSessions::Session.new(payload: payload)
session.flush_by_access_payload
render json: :ok
end
private
def not_found
render json: { error: "Cannot find email/password combination" }, status: :not_found
end
end
/config/initializers/cors.rb
Rails.application.config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
allow do
origins ['https://gem-mint-client.herokuapp.com', 'http://gem-mint-client.herokuapp.com']
resource '*',
headers: :any,
credentials: true,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
/frontend/src/backend/axios/index.js
import axios from 'axios'
const API_URL = 'https://gem-mint-server.herokuapp.com'
const securedAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
const plainAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
securedAxiosInstance.interceptors.request.use(config => {
const method = config.method.toUpperCase()
if (method !== 'OPTIONS' && method !== 'GET') {
config.headers = {
...config.headers,
'X-CSRF-TOKEN': localStorage.csrf
}
}
return config
})
securedAxiosInstance.interceptors.response.use(null, error => {
if (error.response && error.response.config && error.response.status === 401) {
return plainAxiosInstance.post('/refresh', {}, { headers: { 'X-CSRF-TOKEN': localStorage.csrf } })
.then(response => {
localStorage.csrf = response.data.csrf
localStorage.signedIn = true
let retryConfig = error.response.config
retryConfig.headers['X-CSRF-TOKEN'] = localStorage.csrf
return plainAxiosInstance.request(retryConfig)
}).catch(error => {
delete localStorage.csrf
delete localStorage.signedIn
location.replace('/')
return Promise.reject(error)
})
} else {
return Promise.reject(error)
}
})
export { securedAxiosInstance, plainAxiosInstance }
I was dealing with CORS issues, but I don't think that is the case with this issue. My CORS issue was because I inputed my url without https in the browser header, so to fix that I include both https and http url as origins. The end result that I am expecting is that you should be able to login or sign up without being immediately logged out due to not being authorized. If more code is need, please let me know. Thank you in advance.
Edit2: comics_controller.rb
module Api
module V1
class ComicsController < ApplicationController
before_action :authorize_access_request!
before_action :set_comic, only: [:show, :edit, :update, :destroy]
skip_before_action :verify_authenticity_token
# GET /comics
# GET /comics.json
def index
@comics = current_user.comics.all
render json: @comics
end
# GET /comics/marvel
def marvel
# start connection to Marvel
@client = Marvel::Client.new
# keys to configure marvel api
@client.configure do |config|
config.api_key = ENV['PUBLIC_MARVEL_API']
config.private_key = ENV['PRIVATE_MARVEL_API']
end
# store parameters to send as request
# set limit to 100, otherwise server error if over
@args = {limit: '100'}
# marvel api creator parameter needs to be inputted as an id
# find creator id
if params[:creators] != ''
@creator = @client.creators(nameStartsWith: params[:creators])
@args[:creators] = @creator[0][:id]
end
# check if other params were inputted and add to @args object
if params[:issueNumber] != ''
@args[:issueNumber] = params[:issueNumber]
end
if params[:title] != ''
@args[:titleStartsWith] = params[:title]
end
if params[:startYear] != ''
@args[:startYear] = params[:startYear]
end
# find comics based on given parameters
@comics = @client.comics(@args)
render json: @comics
end
# GET /comics/price
def price
EbayRequest.configure do |config|
config.appid = ENV['EBAY_APP_PROD_ID']
config.certid = ENV['EBAY_CERT_PROD_ID']
config.devid = ENV['EBAY_DEV_PROD_ID']
config.runame = ENV['EBAY_PROD_RUNAME']
config.sandbox = false
end
@items = EbayRequest::Finding.new.response('findItemsAdvanced', categoryId: '63', keywords: params[:query])
render json: @items
end
# GET /comics/1
# GET /comics/1.json
def show
@comic
render json: @comic
end
# GET /comics/new
def new
@comic = Comic.new
end
# GET /comics/1/edit
def edit
end
# POST /comics
# POST /comics.json
def create
@comic = current_user.comics.build(comic_params)
respond_to do |format|
if current_user.comics.exists?(title: @comic.title)
# access first object
found_book = current_user.comics.where(title: @comic.title)[0]
quantity = found_book.quantity
# sum up previous quantity with additional quantity
found_book.update_attribute(:quantity, quantity + @comic.quantity)
format.html { redirect_to '/', notice: 'Invite was successfully created' }
format.json {
render json: @comic, status: :created, location: api_v1_comic_url(@comic)
}
else
# if no records exist, create a new one
if @comic.save
format.html { redirect_to '/', notice: 'Invite was successfully created' }
format.json {
render json: @comic, status: :created, location: api_v1_comic_url(@comic)
}
else
format.html { render :new }
format.json {
render json: @comic.errors, status: :unprocessable_entity
}
end
end
end
end
# PATCH/PUT /comics/1
# PATCH/PUT /comics/1.json
def update
respond_to do |format|
if @comic.update(comic_params)
format.html
format.json {
render json: @comic, status: :created, location: api_v1_comic_url(@comic)
}
else
format.html { render :new }
format.json {
render json: @comic.errors, status: :unprocessable_entity
}
end
end
end
# DELETE /comics/1
# DELETE /comics/1.json
def destroy
@comic.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comic
@comic = current_user.comics.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comic_params
params.require(:comic).permit(:title, :issue, :year, :image, {:creators => []}, :quantity, :description)
end
end
end
end
Edit: Log output
at=info method=POST path="/signin" host=gem-mint-server.herokuapp.com request_id=a6f30e31-8f19-4c7f-9381-4135efb0f648 fwd="75.18.100.151" dyno=web.1 connect=1ms service=307ms status=200 bytes=1162 protocol=https
2019-08-07T21:15:03.882596+00:00 app[web.1]: I, [2019-08-07T21:15:03.882487 #4] INFO -- : [a6f30e31-8f19-4c7f-9381-4135efb0f648] Completed 200 OK in 303ms (Views: 0.2ms | ActiveRecord: 1.6ms)
2019-08-07T21:15:04.170728+00:00 heroku[router]: at=info method=GET path="/api/v1/comics" host=gem-mint-server.herokuapp.com request_id=3afecf82-2c42-4e2c-9f25-cffeb88b5017 fwd="75.18.100.151" dyno=web.1 connect=1ms service=4ms status=401 bytes=736 protocol=https
2019-08-07T21:15:04.170050+00:00 app[web.1]: I, [2019-08-07T21:15:04.169938 #4] INFO -- : [3afecf82-2c42-4e2c-9f25-cffeb88b5017] Started GET "/api/v1/comics" for 75.18.100.151 at 2019-08-07 21:15:04 +0000
2019-08-07T21:15:04.170902+00:00 app[web.1]: I, [2019-08-07T21:15:04.170835 #4] INFO -- : [3afecf82-2c42-4e2c-9f25-cffeb88b5017] Processing by Api::V1::ComicsController#index as HTML
2019-08-07T21:15:04.171594+00:00 app[web.1]: I, [2019-08-07T21:15:04.171528 #4] INFO -- : [3afecf82-2c42-4e2c-9f25-cffeb88b5017] Completed 401 Unauthorized in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)
2019-08-07T21:15:04.373727+00:00 app[web.1]: I, [2019-08-07T21:15:04.373626 #4] INFO -- : [82467937-7dbc-4cf2-9cf1-c010f92fad41] Started POST "/refresh" for 75.18.100.151 at 2019-08-07 21:15:04 +0000
2019-08-07T21:15:04.374995+00:00 app[web.1]: I, [2019-08-07T21:15:04.374936 #4] INFO -- : [82467937-7dbc-4cf2-9cf1-c010f92fad41] Processing by RefreshController#create as HTML
2019-08-07T21:15:04.375091+00:00 app[web.1]: I, [2019-08-07T21:15:04.375032 #4] INFO -- : [82467937-7dbc-4cf2-9cf1-c010f92fad41] Parameters: {"refresh"=>{}}
2019-08-07T21:15:04.376098+00:00 app[web.1]: I, [2019-08-07T21:15:04.376018 #4] INFO -- : [82467937-7dbc-4cf2-9cf1-c010f92fad41] Completed 401 Unauthorized in 1ms (Views: 0.3ms | ActiveRecord: 0.0ms)