Vue doesn't fetch data from API in first render - vue.js

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
Network tab
Axios Error
details of request/response
Response of login

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
}
},

Related

Nextjs Auth0 get data in getServerSideProps

Im using Auth0 to authenticate users.
Im protected api routes like this:
// pages/api/secret.js
import { withApiAuthRequired, getSession } from '#auth0/nextjs-auth0';
export default withApiAuthRequired(function ProtectedRoute(req, res) {
const session = getSession(req, res);
const data = { test: 'test' };
res.json({ data });
});
My problem is when I'm trying to fetch the data from getServerSideProps I'm getting 401 error code.
If I use useEffect Im able to get data from api route.
Im trying to fetch the data like this:
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret');
const data = await res.json();
return { props: { data } };
},
});
Im getting the following response:
error: "not_authenticated", description: "The user does not have an active session or is not authenticated"
Any idea guys? Thanks!!
When you call from getServerSideProps the protected API end-point you are not passing any user's context (such as Cookies) to the request, therefore, you are not authenticated.
When you call from useEffect it runs inside your browser, which attaches all cookies to the request, one of them is the session cookie.
You need to forward the session cookie that was passed to the getServerSideProps (by the browser) to the API call.
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret', {
headers: { Cookie: ctx.req.headers.cookie },
// ---------------------------^ this req is the browser request to the getServersideProps
});
const data = await res.json();
return { props: { data } };
},
});
For more info.
#auth0/nextjs-auth0 has useUser hook. This example is from: https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/
// pages/index.js
import { useUser } from '#auth0/nextjs-auth0';
export default () => {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
if (user) {
return (
<div>
Welcome {user.name}! Logout
</div>
);
}
// if not user
return Login;
};
Note that authentication takes place on the server in this model,
meaning that the client isn't aware that the user is logged in. The
useUser hook makes it aware by accessing that information in the
initial state or through the /api/auth/profile endpoint, but it won't
expose any id_token or access_token to the client. That information
remains on the server side.
Custom HOF:
// getData is a callback function
export const withAuth = (getData) => async ({req, res}) => {
const session = await auth0.getSession(req);
if (!session || !session.user) {
res.writeHead(302, {
Location: '/api/v1/login'
});
res.end();
return {props: {}};
}
const data = getData ? await getData({req, res}, session.user) : {};
return {props: {user: session.user, ...data}}
}
Example of using:
export const getServerSideProps = withAuth(async ({req, res}, user) => {
const title = await getTitle();
return title;
});

Nuxt - is it possible to check if a user is logged in from SSR?

I created a Nuxt app that uses Django on the backend, i'm using the standard Django Session Authentication, so when i log in from Nuxt, a session cookie is set in my browser.
I've been trying for days to find a way to restrict some pages to authenticated users only, but i don't seem to find any working approach to do that. I need to check if the user is logged in before the page is loaded, so i tried to use a middleware but middleware won't work at all because the middleware is executed from server side (not client side) so there won't be any cookie in the request.
At this point, is there any other way to do this from SSR? Here is my request:
export default async function (context) {
axios.defaults.withCredentials = true;
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//Check if user is authenticated - response is always False
}).catch(function (error) {
//Handle error
});
}
If you are running Nuxt in SSR mode as server, you can access the cookie headers to find out if the user has a certain cookie. Packages like cookieparser (NPM) can easily do that for you.
But as you already found out, you can't do that in a middleware. What you could use instead is the nuxtServerInit action in your store (Docs). This action is run on the server and before any middleware gets executed. In there you can use cookieparser to get the user's cookies, authenticate them and save the any information you need in the store.
Later you can access the store in your middleware and for example redirect the user.
actually you can get cookies in a middleware.... Ill put my example, but the answer above is more correct .
middleware/auth.js
import * as cookiesUtils from '~/utils/cookies'
export default function ({ route, req, redirect }) {
const isClient = process.client
const isServer = process.server
const getItem = (item) => {
// On server
if (isServer) {
const cookies = cookiesUtils.getcookiesInServer(req)
return cookies[item] || false
}
// On client
if (isClient) {
return cookiesUtils.getcookiesInClient(item)
}
}
const token = getItem('token')
const { timeAuthorized } = cookiesUtils.authorizeProps(token)
const setRedirect = (routeName, query) => {
return redirect({
name: routeName,
query: query
? {
redirect: route.fullPath
}
: null
})
}
// strange bug.. nuxt cant redirect '/' to '/login'
if (route.path === '/') {
setRedirect('users')
}
if (!route.path.match(/\/login\/*/g) && !timeAuthorized) {
setRedirect('login', true)
}
}
utils/cookies.js
import Cookie from 'js-cookie'
import jwtDecoded from 'jwt-decode'
/*
TOKEN
*/
// Get server cookie
export const getcookiesInServer = (req) => {
const serviceCookie = {}
if (req && req.headers.cookie) {
req.headers.cookie.split(';').forEach((val) => {
const parts = val.split('=')
serviceCookie[parts[0].trim()] = (parts[1] || '').trim()
})
}
return serviceCookie
}
// Get the client cookie
export const getcookiesInClient = (key) => {
return Cookie.get(key) || false
}
export const setcookiesToken = (token) => {
Cookie.set('token', token)
}
export const removecookiesToken = () => {
Cookie.remove('token')
}
export const authorizeProps = (token) => {
const decodeToken = token && jwtDecoded(token)
const timeAuthorized = (decodeToken.exp > Date.now() / 1000) || false
return {
timeAuthorized
}
}

Why is my Express res not being returned to Vue.js component?

I'm trying to make an axios HTTP request call to an express route to retrieve a response from passport spotify. I am struggling on sending the response from express to my vue.js component. I am using Nuxt.js.
spotify.vue
export default {
data: function() {
return {
userInfo: null
};
},
mounted() {
this.$axios.get("/auth/spotify")
.then((response) => {
userInfo = response;
});
}
};
server/index.js
const express = require('express');
const passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
const keys = require('../config/keys');
const app = express();
async function start () {
passport.use(
new SpotifyStrategy(
{
clientID: keys.spotifyClientID,
clientSecret: keys.spotifyClientSecret,
callbackURL: '/auth/spotify/callback'
},
function(accessToken, refreshToken, expires_in, profile, done) {
console.log(profile);
}
)
);
app.get('/auth/spotify', passport.authenticate('spotify'), function(req,res) {
res.json(data);
});
app.get('/auth/spotify/callback', passport.authenticate('spotify'));
}
When accessing localhost:3000/auth/spotify the data I am looking for is logged. I am wondering why res.json() or res.send() is not passing the data to the axios promise in my component.
Any help would be appreciated, Thanks in advance!!!

cant import vuex store to request file

i am trying to call a mutation when a request is sent and response has came.
this is my request file:
import axios from 'axios'
import router from '#/router'
import _ from 'lodash'
const instance = axios.create({
baseURL: process.env.BASE_URL,
timeout: 31000,
headers: {
Accept: 'application/json'
},
});
const token = localStorage.getItem('access_token');
if(!_.isNil(token)) {
instance.defaults.headers.Authorization = 'Bearer ' + token;
}
instance.interceptors.response.use(function (response) {
return response
}, function (error) {
if (error.response.status === 401) {
router.push('/introduction')
}
});
export default instance
and this is my main store
const vuexLocal = new VuexPersistence({
storage: window.localStorage
});
Vue.use(Vuex);
axios.defaults.baseURL = 'http://api.balatar.inpin.co/';
export const store = new Vuex.Store({
plugins: [vuexLocal.plugin],
modules: {
user,jobPost, company, application, cvFolder, event
},
state: {
loader:''
},
getters: {
},
mutations: {
LOADER:function (state, payload) {
state.loader=payload;
console.log('MUTATION')
}
},
actions: {
},
});
when i try to import store like below
impotr {store} from '#/store/store'
and then access the LOADER mutation like this:
store.commit('LOADER')
it returns error that cannot read property commit of undefined. how should i do this?
You should write an action, then send your request by your action and as soon as response arrives you will be able to commit a mutation
for example in the following action:
{
/**
* Login action.
*
* #param commit
* #param payload
*/
login: async function ({ commit }, payload) {
commit('LOGGING_IN')
try {
const result = await fetchApi({
url: 'http://api.example.com/login',
method: 'POST',
body: payload
})
commit('LOGIN_SUCCESS', result)
} catch (error) {
commit('LOGIN_FAILURE', error)
}
}
}
as you can see above, as soon as you call login, it calls LOGGING_IN mutation and sends a request to some address, then it waits for a response.
if it gets success response the LOGIN_SUCCESS mutation with a payload of result commits otherwise it commits LOGIN_FAILURE with a payload of cached error.
note: you should provide your own fetchApi method which is a promise.

NuxtJs - Cannot read property 'headers' of undefined

I'm a newbie in NuxtJs. I'm trying to implement an external API Call with axios which I get token and store it on cookie. Everything works well in development. But when I try to run npm run generate it gives me errors that I don't know what to do.
When I delete nuxtSeverInit, npm run generate runs smoothly. And after some research, i think that nuxtServerInit that I'm using shouldn't be used. Can anyone please tell me how to make it work.
This is the first project in a new company, so I'm trying to prove myself. Please help me with it. Will you.
Click here for image that shows the error that appears after npm run generate
This is store/index.js file
import Vuex from 'vuex'
var cookieparser = require('cookieparser')
const createStore = () => {
return new Vuex.Store({
state: {
auth: null,
},
mutations: {
update (state, data) {
state.auth = data
}
},
actions: {
nuxtServerInit ({ commit }, { req }) {
let accessToken = null
if (req.headers.cookie) {
var parsed = cookieparser.parse(req.headers.cookie)
if(parsed){
accessToken = parsed.auth
}
}
commit('update', accessToken)
},
}
})
}
export default createStore
middleware/authenticated.js file
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.auth) {
return redirect('/login')
}
}
middleware/notAuthenticated.js file
export default function ({ store, redirect }) {
// If the user is authenticated redirect to home page
if (store.state.auth) {
return redirect('/app/dashboard')
}
}
login.vue file
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
this.button_title = 'One moment ...';
let submitted_user_data = {
'username': this.emailAddress,
'client_id': this.user_uuid,
'password': this.password,
}
MerchantServices.do_user_login(submitted_user_data)
.then(response => {
let access_token = response.data.access_token;
this.postLogin(access_token);
})
.catch(error => {
this.$refs.invalid_credentials.open();
this.button_title = 'Sign in'
});
return;
}
});
},
postLogin: function(access_token_val) {
if(access_token_val != ''){
setTimeout(() => {
const auth = {
accessToken: access_token_val
}
this.$store.commit('update', auth)
Cookie.set('auth', auth)
this.$refs.invalid_credentials.open();
this.button_title = 'Sign in'
this.$router.push('/app/dashboard')
}, 1000)
}else{
alert('hello')
}
},
and the last user login api call which also returns the token.
do_user_login(user){
var user_details = 'username='+user.username+'&client_id='+ user.client_id +'&grant_type=password&password='+user.password+''
return axios.post('myapiurl', user_details )
.then(response => {
return response;
});
},
Acording to Nuxt Docs req is not available on nuxt generate.
You should use nuxt build and than nuxt start after that.