0

Current situation

I am developing nodejs backend server and vue frontend application, which is run under different port(localhost:3000 and localhost:8080). With purpose to enable CORS connection, I configured devServer proxy from vue.config.js file.

vue.config.js

module.exports = {
    devServer: {
        proxy: {
            '/users': {
                target: 'http://127.0.0.1:3000/users',
                changeOrigin: true,
                pathRewrite: {
                    '^/users':''
                }
            },
            '/tasks': {
                target: 'http://127.0.0.1:3000/tasks',
                changeOrigin: true,
                pathRewrite: {
                    '^/tasks': ''
                }
            }
        }
    },
    outputDir: '../backend/public'
}

and technically used cors.js to enable request to backend server, which was implemented by expressjs. I am sending the request with vue component to retrieve data from backend server. It works properly from fetching data from server, and my goal is to make the same behavior when I reload page. However, whenever I reload same page, it keep showing 401 http response status set by the backend code written by myself.

enter image description here

Research and Trial til now

Before I go on the attempts I have tried, I should introduce mandatory codes to be operated at first. Somehow this is at least explanations in which vuex actions using axios, axios using backend routers eventually.

tasks.module.js

import axios from "axios"
import authHeader from '../../services/auth-header'

export const tasks = {
    state: {
        tasks: []
    },

    getters: {
        allTasks: (state) => state.tasks
    },

    actions: {
        async fetchTasks({ commit }) {
            const response = await axios.get('http://127.0.0.1:3000/tasks', {headers: authHeader()})

            commit('setTasks', response.data)

            axios.defaults.headers.common['Authorization'] = authHeader()
        },

        async addTask({ commit }, description) {
            const response = await axios.post('http://127.0.0.1:3000/tasks', { description, completed: false}, {headers: authHeader()})

            commit('newTask', response.data)
        },

        async updateTask({ commit }, updTask) {
            const response = await axios.patch('http://127.0.0.1:3000/tasks/'+updTask.id, updTask, {headers: authHeader()})

            commit('updateTask', response.data)
        }
    },

    mutations: {
        setTasks: (state, tasks) => (state.tasks = tasks),
        newTask: (state, task) => state.tasks.unshift(task),
        updateTask: (state, updTask) => {
            let updates = Object.keys(updTask)

            updates.forEach((update) => {
                state.task[update] = updTask[update]
            })
        }
    }
}

TaskManager.vue

<template>
  <div class="container">
    <div class="jumbotron">
      <h3>Task Manager</h3>
      <AddTask/>
      <Tasks/>
    </div>
  </div>
</template>

<script>
import Tasks from './components/Tasks'
import AddTask from './components/AddTask'

export default {
  name:'TaskManager',
  components: {
    Tasks,
    AddTask
  }
}
</script>

Tasks.vue

<template>
<div>
    <div>
        <div class="legend">
            <span>Double click to mark as complete</span>
            <span>
                <span class="incomplete-box"></span> = Incomplete
            </span>
            <span>
                <span class="complete-box"></span> = Complete
            </span>
        </div>
    </div>
    <div class="tasks">
        <div
            @dblclick="onDblClick(task)"
            v-for="task in allTasks"
            :key="task.id"
            class="task"
            v-bind:class="{'is-completed':task.completed}">
            {{task.description}}
        </div>
    </div>
</div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'
export default {
    name: "Tasks",
    methods:{
        ...mapActions(['fetchTasks', 'updateTask']),
        onDblClick(task) {
            const updTask = {
                id: task._id,
                description: task.description,
                completed: !task.completed
            }
            console.log(updTask)
            this.updateTask(updTask)
        }
    },
    computed: {
        ...mapGetters(['allTasks']),
    },
    created() {
        this.fetchTasks()
    }
}

Now I need to introduce what I have tried to solve problems

  1. Configuring CORS options

    Since this error page didnt show any authorization header which was supposed to set in request header I figured out the way I enabled cors connection and I believe this enables preflight request. Here is what I configured middleware behavior from backend code. task.js(express router file)

const router = new express.Router()
const auth = require('../middleware/auth')
const cors = require('cors')
const corsOptions = {
    origin: 'http://127.0.0.1:3000',
    allowedHeaders: 'content-Type, Authorization',
    maxAge:3166950
}
router.options(cors(corsOptions))

router.get('/tasks', auth, async (req, res) => {
    const match = {}
    const sort = {}

    if(req.query.completed) {
        match.completed = req.query.completed === 'true'
    }

    if(req.query.sortBy) {
        const parts = req.query.sortBy.split('_')
        sort[parts[0]] = parts[1] === 'desc' ? -1:1             // bracket notation
    }

    try {
        await req.user.populate({
            path: 'tasks',
            match,
            options: {
                limit: parseInt(req.query.limit),
                skip: parseInt(req.query.skip),
                sort
            }
        }).execPopulate()
        console.log(req.user.tasks)
        res.status(200).send(req.user.tasks)
    } catch (e) {
        res.status(500).send(e)
    }
})
module.exports = router

auth.js(middleware)

const jwt = require('jsonwebtoken')
const User = require('../models/user')

const auth = async (req, res, next) => {
    try {
        const token = req.header('Authorization').replace('Bearer ','')
        const decoded = jwt.verify(token, 'thisisnewcourse')
        console.log('decoded token passed')
        const user = await User.findOne({ _id: decoded._id, 'tokens.token': token})
        console.log('user found')

        if(!user) {
            throw new Error()
        }
        req.token = token
        req.user = user
        next()

    } catch (error) {
        console.log('error caught')
        res.status(401).send({error: 'please authenticate'})
    }
}

module.exports = auth
  1. Set Authorization header as axios default header after login

auth.module.js(since login works correctly, I am copying only login action part)

actions: {
        async login ({ commit }, user){
            try {
                const response = await axios.post('http://127.0.0.1:3000/users/login', user)

                if(response.data.token){
                    localStorage.setItem('user', JSON.stringify(response.data))

                    axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`
                }

                commit('loginSuccess', response.data)
            } catch (e) {
                console.log(e)
            }


        }
  1. Middleware chaining on the express route(cors, auth)

I have tried two different middleware on the same backend code(task.js)

router.get('/tasks', [cors(corsOptions), auth], async (req, res) => {
    // same as previously attached code
}

Now I believe referring to another post with similar issue will help me out however it's about having CORS enabled, not the issue that the header is not sent via either preflight request or other type of requests.

1 Answers1

0

You haven't included the code for authHeader but I assume it just returns the value of the Authorization header.

This bit looks suspicious to me:

async fetchTasks({ commit }) {
  const response = await axios.get('http://127.0.0.1:3000/tasks', {headers: authHeader()})

  commit('setTasks', response.data)

  axios.defaults.headers.common['Authorization'] = authHeader()
},

The final line seems to be trying to set the Authorization header globally so that it will be included on all subsequent axios requests. That's fine but it seems strange not to do that sooner. You have a similar line inside the login action, which makes sense, but I assume that isn't being called when the page is refreshed.

Then there's this bit:

{headers: authHeader()}

If authHeader returns the value of the Authorization header then this won't work. Instead you need:

{headers: { Authorization: authHeader() }}

Ideally you wouldn't need to set any headers here and instead you'd just set the global header before attempting this request.

While it isn't the direct cause of your problem, you seem to have got your wires crossed about CORS. You've configured a proxy, which means you aren't using CORS. The request you're making is to the same origin, so CORS doesn't apply. You don't need to include CORS response headers if you aren't making a cross-origin request. If you do want to make a cross-origin request then don't use the proxy. You should try to mimic your production environment during development, so if you intend to use CORS in production you should use it during development. Otherwise, stick with the proxy.

skirtle
  • 27,868
  • 4
  • 42
  • 57
  • Thank you for your answer @skirtle. FYI, I did put {Authorization : Bearer `${user.token}`} authHeader() returns. However it keeps showing 401 error message on router code. The big difference I have met since I applied advice from you is web browser now show me navigation bar and TaskManager.vue view. Hopefully this one means Ive got your help to narrow the matter of Task.vue where the action 'fetchTask' occurs. I have chased down error message from console to see how axios' role works behind the code, but it's not enough – Jung Hyun Lee Mar 16 '20 at 11:47
  • @JungHyunLee The picture you posted originally showing the headers showed that there was no `Authorization` request header being sent to the server. That is where you need to focus your debugging efforts. Without that header you're just going to get a 401 from the server. There are several different problems that could cause a 401 so even if you fix one of those problems you may still see the same error message. Keep checking the request headers in the developer tools, that's a much more reliable way to debug this problem. A bit of console logging and you'll have it fixed in no time. – skirtle Mar 16 '20 at 12:45
  • I FINALLY found expected result !! These are what I tried for solving problem. It has been almost a week from the beginning and I feel so satisfied. 1. I put middleware back in the router code to console.logging back(now it's okay to use it again) / 2. changed async functio to normal function where of course to use promise(now it's okay to use it again) / 3. Because browser keep showing me CORS issue guessing cuz of 'Authorization' Header so it's unusual header that calls preflight request(I have no doubt CORS forces preflight request) /.. continue – Jung Hyun Lee Mar 17 '20 at 05:10
  • so I omitted devServer proxy from vue.config.js and set response header inside of auth middleware writing (res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Header', 'Content-Type') res.header('Access-Control-Allow-Header', 'Authorization')) and it was totally okay to set preflight header to allow Authorization header. Lastly, when I console.logged axios default header, I set header improperly from login action.. so I corrected from ${user.token} to ${response.data.token} @skirtle – Jung Hyun Lee Mar 17 '20 at 05:14