I have an frontend Web app interfaces with API built in Laravel with Passport.
My problem is when I refresh my page (in SPA written with Vuejs/Vuex) I should refresh my token, for refresh session with my Api.
I tried in main.js but he problem is that the request is async and the response arrived after routing.
main.js
if (localStorage.getItem('refresh_token')) {
store.dispatch('refresh_token').then(function(response){
console.log(response);
});
}
new Vue({
router,
store,
env,
render: h => h(App)
}).$mount('#app')
The function refresh token, make a call to my Api, and with response set the new token and the new refresh token.
But my problem is that I make this call in this way I can make the first async call in my "dashboard" with old token and then with the new.
So I've tried in different ways but I don't know if there is a best practice.
So my question is:
Where I should refresh token in Vuejs App with vuex store?
I suggest putting this in the mounted property of you toplevel Vue component. If you have other components that depend on your token being refreshed, you can couple this with a state variable in your store that signals the refresh is completed. For example, with a top level component App.vue:
...
mounted () {
store.dispatch('refresh_token')
}
...
Adding the state variable to your vueex store:
const store = new Vuex.Store({
state: {
sessionRefreshed: false
},
..
mutations: {
[REFRESH_TOKEN] (state) {
// existing mutations, and..
state.sessionRefreshed = true
},
},
..
actions: {
refreshToken ({ commit }) {
myAsyncFetchToken().then(() => commit(REFRESH_TOKEN))
},
}
This ensures your entire application is aware of the state of your refresh without forcing it to be synchronous. Then if you have components which require the token to be refreshed, you can show loading widgets, placeholders, etc., and use a watcher to do things when the state changes.
How about using router#beforeEach guard? I use it to figure out if authentication token is stored in a cookie before accessing any "restricted" component. If token is not set I redirect to /login.
I realize that my scenario is exactly what you are are asking for but I hope you can use it to augment your implementation.
Related
In my application I have a Vuex 4 store and a Vue 3 Composition Api setup() method.
In the stores action I use axios to make an api call to get a list of bill payments.
The getAllBills action does not live directly in my Store.js file, it exists as a module.
getAllBills({ commit }) {
BillApiCalls.getBills().then(res => {
commit('GET_ALL_BILLS', res.data)
}).catch(error => console.log(error))
},
Then in my Bill.vue file I have the setup() method and am trying to access the data to be used throughout the same Bill.vue file.
setup () {
//Vuex store
const store = useStore();
const billPayments = store.dispatch('payment/getAllBills').then(res => console.log(res));
}
If I check the console from the above .then() res returns as undefined. If I remove the .then() from the billPayments declaration and just do:
console.log(billPayments)
In the console I get
Promise {<pending>}.
Current Store:
import { bill } from './modules/bill.module';
const store = createStore({
modules: {
bill
}
});
The endpoint is working, if I use Postman all of my data is returned as expected but I am having trouble figuring out how to access that data using a dispatched action with the composition api.
The Vuex 4 docs don't mention how to actually resolve the promise to access the data to be used throughout the same component.
An action isn't generally supposed to return data it acts on, data is a part of the state and should be accessed there; this is what another answer shows:
await store.dispatch('payment/getAllBills')
console.log(store.state.payment.bills);
The action doesn't chain promises so it cannot be correctly used. It should be:
return BillApiCalls.getBills()...
Or prefer async..await together with promise to avoid some common mistakes that can be made with raw promises.
I am building a SPA using Vue-CLI with a client-side OAuth 2.0 javascript library called JSO. It uses HTML 5.0 localStorage to cache Access Tokens.
In my full app, I have everything functioning properly with the exception of the following issue:
When the user arrives at my app for the first time, he catches a quick glimpse of the app and then automatically is redirected to a third party authentication login screen. I don't want that "quick glimpse" to happen -- I need to have the user immediately redirected to the third party login page BEFORE he sees any part of my app.
So, I thought I'd set up Global Before Guards using Vue-Router like so:
From: Main.js
const routes = [
{
path: '/',
name: 'home',
component: Home,
meta: {
requiresAuth: true
}
},
...more routes...and they all require auth...
]
router.beforeEach((to, from, next) => {
const token = window.localStorage.getItem('my-token-example')
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
if (token == null) {
client.getToken()
next(false)
}
} else {
next()
}
})
new Vue({
created: function() {
//check for response parameters if user has an auth token (uses JSO plugin)
//if token received, then it is stashed into localStorage
client.callback()
},
render: h => h(App),
router: router
}).$mount('#app')
Example App.vue:
<template>
<div id="app">
<Header />
<routerView />
</div>
</template>
Info on client.callback():
I catch the headers response when user is returning to app
Info on client.getToken():
I get the token payload
Notes: The JSO auth docs state:
"You may also ensure that a token is available early in your application, to force all user interaction and redirection to happen before your application is fully loaded. To do that make a call to getToken, and wait for the callback before you continue.
REMEMBER to ALWAYS call the callback() function to process the response from the OAuth server, before you use getToken(), if not you will end up in an redirect_loop"
Does anyone have any suggestions on how I can prevent the user from seeing my app before he is redirected to the auth login site? Update: I think I see the problem...is the global guards only affecting the section of the app within the <RouterView /> component? Hence, we see the header and banner of my app before redirection?
I solved this. I had to simply do a v-if on my app.vue file like: <div v-if="token !== null>
That hides the app template until token is received.
Is there a way to change the Apollo endpoint after having created the Apollo client? I would like to have the user to input their own endpoint.
httpEndpoint can be a function, and it is called on each query.
As #wrod7 mentioned, you could use localStorage, but global variable should be enought
// vue-apollo.js
export { defaultOptions }
// main.js
import { createProvider, defaultOptions } from './vue-apollo'
window.apolloEndpoint = defaultOptions.httpEndpoint
new Vue({
router,
apolloProvider: createProvider({
cache,
httpEndpoint: () => window.apolloEndpoint,
}),
render: h => h(App),
}).$mount('#app')
and you can set apolloEndpoint anywhere you like, i am using window. to make eslint happy
httpEndpoint is called with current query, so you can conditionally return endpoint based on operationName
vue-apollo got support for multiple clients, it might be useful if you want to override endpoint only on some queries, but i thinks this offtopic
I am doing this on one of my applications. I store the URL string in localstorage or asyncstorage on mobile. you can check for the string when the app loads and have the user enter one if there isn't a url string stored. the application would have to refresh after entering the url and saving it to localstorage.
I have a simple use case, where my application is using vue-router and vuex. Then store contains a user object which is null in the beginning. After the user is validated from the server it sends back an user object which contains a JWT auth token which is assigned to the user object in the store. Now lets assume that the user came back after 3 hours and tried to visit a route or perform any other action, considering that the auth token has expired by then, what would be the best way to check that(need to call axios post to check it) and redirect user to the login page. My app will have loads of components so I know I can write logic to check the token valid in the mounted hook of each component but that would mean repeating it all of the components. Also I don't want to use the beforeEach navigation guard because I cannot show any visual feedback to the user like checking... or loading....
I do something similar in one of my projects, it's actually deceptively difficult to handle these types of situations, but you can add a beforeEnter guard to your protected routes, then redirect if the authentication failed.
const guard = function(to, from, next) {
// check for valid auth token
axios.get('/api/checkAuthToken').then(response => {
// Token is valid, so continue
next();
}).catch(error => {
// There was an error so redirect
window.location.href = "/login";
})
};
Then on your route you can do:
{
path: '/dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
guard(to, from, next);
}
},
You may notice I've used location.href rather than router.push. I do that because my login form is csrf protected, so I need a new csrf_token.
Your other issue is going to be if the user tries to interact with your page without changing the route (i.e. they click a button and get a 401 response). For this I find it easiest to check authentication on each axios request and redirect to login when I receive a 401 response.
In terms of adding a loading spinner during the guard check you can simply add a loading flag to your vuex store then import your store into your router. Honestly though I wouldn't bother, on a decent production server the check will be done so quickly that the user is unlikely to ever see it.
Try Vue.JS Mixins
You can define a Global Mixin and use it via Vue.use(myMixin) - then all Components will inherit this mixin. If you define a mounted or probably better activated hook on the mixin, it will be called on every component.
There you can use everything a component can do - this will point to your component. And if the component also defines a hook itself, the mixin hook of the same type will run before the components own hook.
Or try a single top-level login component
We used a little different solution - we have a single component which handles everything login-related, which exists outside of the router-view in the parent index.html. This component is always active and can hide the div router-view and overlay a loading message or a login-screen. For an intranet-application this component will also use polling to keep the session alive as long as the browser stays open.
You can load of your router-navigation to this component. - So a child-component which wants to trigger a router-navigation just sets a global reactive property navigateTo which is watched by the top level authentication component. This will trigger an authentication check, possibly a login-workflow and after that the top-level component will call $router.push() With this approach you have complete control over any navigation.
You can use interceptors to silently get the auth token when some request happens.
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const rToken = window.localStorage.getItem('rToken');
return axios.post('url/to/get/refresh/token', { rToken })
.then(({data}) => {
window.localStorage.setItem('token', data.token);
window.localStorage.setItem('rToken', data.refreshToken);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.token;
originalRequest.headers['Authorization'] = 'Bearer ' + data.token;
return axios(originalRequest);
});
}
return Promise.reject(error);
});
Because you use vuex, you can add some state like isLoading or isChecking.
And in your router.beforeEach, you can check and set isLoading or isChecking follow your current checking state. Then you can show loading message follow this state.
In our route.js we check in beforeEnter hooks the user has token or
not.
route.js
{
path: '/dashboard',
name: dashboard,
meta: {
layout: 'home-layout'
},
components: {
default: Dashboard,
header: UserHeader
},
beforeEnter: ifAuthenticated,
}
route.js
const ifAuthenticated = (to, from, next) => {
if (localStorage.getItem(token)) {
next();
return;
}
router.push({
name: 'login',
params: {
returnTo: to.path,
query: to.query,
},
});
};
I'm building an app using VueJS and Electron, now I'm trying to create a login using the #websanova/vue-auth package and everything goes well (login, logout, route protection, etc..) the only thing I'm stuck on is that everytime I log in and refresh or restart electron, it kicks me back to the login page. The weird thing is, it refreshed the token successfully, if I look in the localstorage it is updated and when I try to make a manual request using a REST client the token works. I just can't get into the app using that token.
I'm using the latest versions of VueJS, vue-router, vue-resource and #websanova/vue-auth as of today (19-sep-2016).
The API side is a Laravel 5.3 installation and I'm using the tymondesigns/jwt-auth package to handle the JWT tokens.
this is how I use my routes:
'/': {
auth: true,
name: 'dashboard',
component: HomeView
},
'/login': {
auth: false,
name: 'login',
component: LoginView
}
The views are being compiled using browserify and vueify.
My login function is like this:
this.$auth.login({
body: this.body,
success: function () {
this.loading = false;
},
error: function () {
this.error.status = true;
this.loading = false;
this.body.password = '';
},
rememberMe: true
});
If you need more information in order to be able to help me, just let me know.
Edit: If you want to take a look, here are the links to the repo's:
API: https://github.com/luukhoeben/rmi-app-api
Electron app: https://github.com/luukhoeben/rmi-app-electron
Thanks,
Luuk
If you set
Vue.use(Auth, {
router: Router,
http: Vue.$http,
tokenExpired: () => {
return false
}
})
Setting tokenExpired to return false, like this, you will skip the token refresh all together, which is not that bad. Downside is that your clients will have an expired token at some point and will be forced to re-login.
Another method is to try and check when the token will expire and refresh based on that.