Othmane
Othmane

Reputation: 179

Vue doesn't fetch data from API in first render

After logging in I call await router.push('/'); to redirect to the home page where I load users and I get this error GET http://localhost:8080/users 401 then when I refrehs the page in the exact same component I get the data just fine with a 200 status. I'm not sure what's going on

async login (username, password) {

    const response = await axios.post('/auth/login', {
        username: username,
        password: password
    });

    this.user = response.data;
    localStorage.setItem('user', JSON.stringify(this.user));

    await router.push('/');
},

This is the function I call after logging in

This is the router.js

import { createRouter, createWebHistory } from 'vue-router';
import Login from '../views/Auth/Login.vue';
import { useAuthStore } from '../stores/auth.store.js';
import IndexUser from "../views/Users/IndexUser.vue";
import IndexHive from '../views/Hives/IndexHive.vue';

const routes = [
    { path: '/', name: 'Home', component: IndexUser },
    { path: '/login', name: 'Login', component: Login },
    { path: '/users', redirect: { name: 'Home' } },
    { path: '/users/create', name: 'CreateUser', component: CreateUser },
    { path: '/hives', name: 'IndexHive', component: IndexHive }

];

import CreateUser from '../views/Users/CreateUser.vue';

const router = createRouter({
    history: createWebHistory(),
    routes
});

router.beforeEach(to => {

    const authStore = useAuthStore();

    const publicPages = ['/login'];
    const authRequired = !publicPages.includes(to.path);

    if (authRequired && !authStore.user) {
        return '/login';
    }
})

export default router;

This is the component I redirect to after logging in

onMounted( async () => {
  const response = await axios.get('/users');
  users.value = response.data;
})

Devtools

enter image description here

Network tab

enter image description here

Axios Error

enter image description here

details of request/response

enter image description here

enter image description here

Response of login

enter image description here

Upvotes: 0

Views: 1094

Answers (1)

Daniel
Daniel

Reputation: 35674

Update 2

Having seen the code, I think the problem is here:

import axios from "axios";

axios.defaults.baseURL = import.meta.env.VITE_API_URL;

if (localStorage.getItem('user')) {
    const user = JSON.parse(localStorage.getItem('user'));
    axios.defaults.headers.common['Authorization'] = `Bearer ${user?.accessToken}`;
}

this will read the axios.defaults.headers when the helpers/axios.js file is loaded. This is why axios.get('/users'); only works on second load, or rather only when the authentication is already loaded into localStorage. A change to the user object or a local storage will not update since this code only runs once at the beginning, the change to axios.defaults.headers needs to be dynamic.

Update

if setTimeout didn't work that could be due to a different issue. Also, if your request works a second time, but it also works if the authentication is passed directly, it seems to me that it has something to do with the authentication being handled implicitly.

I think what's happening is that you are creating multiple instances of axios and relying on shared authentication

// create single axios instance
export const api = axios.create({
   withCredentials: true,
   baseURL: BASE_URL // optional
})

// then use
await api.post('/auth/login', {
  username: username,
  password: password
});

// and 
await api.get('/users');

This might make the axios instance remember the authentication information between calls. It may still require handling race condition if you have an app that doesn't wait on the login request to finish.


I think this is just an issue with a race condition

POST:/login and GET:/users requests appear to be done in parallel.

onMounted( async () => {
  // this should wait until the `login` has been handled
  const response = await axios.get('/users');
  users.value = response.data;
})

I don't see how you call login so can't offer the the exact solution, but if you can store the login request state as a reactive variable, you can do something like

watch: {
  loginState:{
    immediate: true
    handler(value){
      if (value === LOADED) {
        const response = await axios.get('/users');
        users.value = response.data;
      }
    }
  }
})

here's what the changes to the authStore might look like

export const STATES = {
  INIT:"INIT",
  PROCESSING:"PROCESSING",
  ERROR:"ERROR",
  LOADED:"LOADED",
}
export const loginState = ref(STATES.INIT);

async login (username, password) {
    loginState.value = STATES.PROCESSING
    try{
      const response = await axios.post('/auth/login', {
        username: username,
        password: password
      });
      loginState.value = STATES.LOADED
      this.user = response.data;
      localStorage.setItem('user', JSON.stringify(this.user));

      await router.push('/');
    }catch(e){
      // handle error
      loginState.value = STATES.ERROR
    }
},

Upvotes: 1

Related Questions