I have a VueJS project where I need to check periodically a function to see if a token has expired once the user login to the app successfully and if the token has expired have to show a modal message to user.
I have my Singin.vue file that contains the following code:
....
methods: {
...mapActions(['authorize']),
submit() {
this.$validator.validateAll().then(result => {
if (result) {
this.error = null;
this.processing = true;
this.authorize(this.credentials).then(() => {
// ***********
// HERE I have to check periodically if the token has expired
// ***********
this.$router.push({name: 'home'});
}).catch(error => {
console.warn('error message', error);
this.error = error.response.data.message;
this.processing = false;
});
}
});
}
When this.authorize happens I route to home, but before that happens I need to start calling a function periodically. Then If user Logoff then I have to clear the interval.
So first, I don't know where is the best place to have this TokenExpiration function code. Does it make sense to have it in a store file?
This is my api.js store file where I have my authorize function and my logout function, does it make sense to have the tokenExpirationCheck function here also?
There are several ways of doing it, but I would probably solve this using a plugin, because timers should not be in the store, and the behavior is global to the application, so I wouldn't put it into any single component.
The pugin would have a vuex.watch on the stoere's logged-in flag. When it goes from false => true, remove the timer (if active) and if it goes from false => true, add the timer. The timer function can then call the vuex dispatch to handle the functionality.
Related
i am using Vue 2 / nuxt to emit from a axios post* call which itself is called from a Buefy dialog confirm. The emit from this component will close the window / panel and then re-load the users.
If I call the axios request from the button, this works without any issues, but once being called from the dialog, it just don't work?
*most likely this will be updated to a delete request, just not gotten to that let
See code below:
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: () => {
this.removeUserFunc()
}
})
},
removeUserFunc() {
// console.log(that)
const that = this
// Build URL
const EndPoint = '/remove_user/' + this.id
this.$axios.post(EndPoint).then((res) => {
// User Remove Message
UserRemoved(this.$swal)
that.$parent.$emit('completed')
// console.log(this.$emit('complete'))
// // Emit 'completed' Message
console.log(that.$emit('completed'))
console.log(that)
}).catch((res) => {
console.log(res)
// Check For Errors
GeneralError(this.$swal)
})
}
I was thinking it was losing access to the correct this, so i was trying to pass that back in, but not sure that is the case?
I have also tried with await, while that sort of works? I think is firing the emit too fast, as it re-loads the users but it still includes the user that as just been deleted?
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: async() => {
this.removeUserFunc()
await this.$emit('completed')
}
})
},
The this keyword refers to the object the function belongs to, or the window object if the function belongs to no object.
Try to use .bind and use a ES5 function
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: function() {
this.removeUserFunc()
}.bind(this)
})
},
I'm going to use SSE to implement real-time notifications.
Please look at my method and tell me what the problem is and how to solve it.
in vuex login action method
// SSE EvnetSource Connect
let url = process.env.VUE_APP_API_URL + "subscribe";
let eventSource = new EventSource(url, {
withCredentials: true
});
eventSource.addEventListener("notification", function (event) {
console.log(event.data);
commit('setNotification', event.data) // => set event's data to vuex 'notification' state as array
});
and then
in top nav component's watch method
watch: {
notification(val) {
if(val) {
const notiData = JSON.parse(val)
if(notiData.id) {
// show notification alert component
this.$notify("info filled", "notification", notiData.content, null, {
duration: 7000,
permanent: false
});
}
}
}
}
This is my current situation.
And this is my questions.
Currently, when logging in through vuex, I create an EventSource, but how to delete the EventSource when logging out? (EventSource is not defined globally, so I don't know how to approach it when logging out).
How to reconnect EventSource after page refresh? (I think main.js can handle it.)
Is there a way to put in Custom Header when creating EventSource?
As any other event bus, EventSource needs to be unsubscribed when events shouldn't be received. This requires to keep a reference to listener function. If a listener uses Vuex context that is available inside an action, it should be defined inside login action and stored in a state:
const notificationListener = (event) => {...};
eventSource.addEventListener("notification", notificationListener);
// can be moved to a mutation
state._notificationEventSource = eventSource;
state._notificationListener = notificationListener;
Inside logout action:
let { _notificationEventSource: eventSource, _notificationListener: notificationListener } = state;
eventSource.removeEventListener("notification", notificationListener);
It's no different when a page is initially loaded and reloaded.
What is the correct pattern to implement Auth0 route guards in Nuxt?
I've adapted the Auth0 sample code to create the following middleware:
import {getInstance} from '~/plugins/auth';
export default function () {
const authService = getInstance();
const fn = () => {
// If the user is authenticated, continue with the route
if (!authService.isAuthenticated) {
authService.loginWithRedirect({
appState: {targetUrl: 'http://localhost:3000'},
});
}
};
// If loading has already finished, check our auth state using `fn()`
if (!authService.loading) {
return fn();
}
// Watch for the loading property to change before we check isAuthenticated
authService.$watch('loading', loading => {
if (loading === false) {
return fn();
}
});
}
Notice that before the authentication status of Auth0 can be accessed, we must wait for the the instance to finish loading. The Auth0 sample code does this by using $watch.
My middleware code "works" but has the issue of briefly displaying the protected pages before the async $watch triggers. Is there any way to wait and block the route from continuing to render until Auth0 has finished loading and its auth status can be accessed?
I've also tried using almost the exact same code Auth0 provides without my own modifications within the beforeRouteEnter hook of the Nuxt pages. This has the same issue which begs the question as to why the Auth0 example presumably works in VueJS using beforeRouteEnter but not in Nuxt?
Solved it!
A middleware can be asynchronous. To do this return a Promise or use async/await.
https://nuxtjs.org/docs/2.x/directory-structure/middleware/
I simply wrapped my middleware script in a promise. I resolved it if the user is able to pass, otherwise I redirected them to the Auth0 login.
import {getInstance} from '~/plugins/auth';
export default function () {
return new Promise(resolve => {
const authService = getInstance();
const fn = () => {
// If the user is authenticated, continue with the route
if (!authService.isAuthenticated) {
return authService.loginWithRedirect({
appState: {targetUrl: 'http://localhost:3000'},
});
}
resolve();
};
// If loading has already finished, check our auth state using `fn()`
if (!authService.loading) {
return fn();
}
// Watch for the loading property to change before we check isAuthenticated
authService.$watch('loading', loading => {
if (loading === false) {
return fn();
}
});
});
}
It was also important to return the loginWithRedirect to make sure that it didn't go on to resolve the promise outside of the if block.
I'm using NUXT middleware to check if a user is logged in or not, and protect certain routes accordingly. The problem is, when a logged-in user refreshes the page on one of the protected routes, the session is lost.
I have a getter in my Vuex store state (using NUXT):
getters: {
isLoggedIn (state) {
return !isEmpty(state.auth.email) && !isEmpty(state.auth.token)
}
}
I'm accessing this getter in middleware to redirect unauthenticated users to a login page:
let isLoggedIn = context.store.getters.isLoggedIn
if (!isLoggedIn && protectedRoutes.includes(context.route.name)) {
let language = context.store.language ? context.store.language : 'en'
context.redirect(`/${language}/login`)
}
But it's not working. When I console.log() the value of this getter, I get TRUE on the client side and FALSE on the server side. How can I keep them both in sync with Vue/Vuex?
Furthermore, whenever I console.log() the context object on the server side, it appears to be in its initial state. There must be something fundamentally wrong with my approach.
When user refresh a page all vuex state is lost and start from new. You need to initialize user somewhere like nuxtServerInit
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}
I have two routes — one is a public router and the other is the authorized router.
In my authorized router I have an authorizeStep.
The authorizedStep checks if there is a token in localStorage and if it is returns a next().
However if it fails to find a token its supposed to stop and jump out returning to the public router.
I am having trouble stopping the authorized step and instead going to the public router.
I have:
run(navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
return Promise.resolve()
.then(() => this.checkSessionExists(navigationInstruction, next)
.then(result => result || next())
);
}
checkSessionExists(navigationInstruction: NavigationInstruction, next: Next) {
const session = this.authService.getIdentity();
if (!session) {
// HOW DO I CANCEL THE NEXT HERE AND GO TO THE PUBLIC ROUTER?
return next.cancel(new Redirect('login'))
}
return next()
}
forceReturnToPublic() {
this.authService.clearIdentity();
this.router.navigate("/", { replace: true, trigger: false });
this.router.reset();
this.aurelia.setRoot("public/public/public");
}
I have the function forceReturnToPublic() however I want to go and cancel the next() then go directly to the other router... I dont want to redirect..
How do I cancel the next in the promise and reset the router?
Here is my boot.ts which should kick it back to public but I dont know how to jump out of the promise cleanly...
// After starting the aurelia, we can request the AuthService directly
// from the DI container on the aurelia object. We can then set the
// correct root by querying the AuthService's checkJWTStatus() method
// to determine if the JWT exists and is valid.
aurelia.start().then(() => {
var auth = aurelia.container.get(AuthService);
let root: string = auth.checkJWTStatus() ? PLATFORM.moduleName('app/app/app') : PLATFORM.moduleName('public/public/public');
aurelia.setRoot(root, document.body)
});
If I place the forceReturnToPublic() in place of the return next.cancel(new Redirect('login') it goes into and endless loop with errors.
EDIT
I found THIS question which indicates I should add "this.pipeLineProvider.reset()" so I did - like this...
forceReturnToPublic() {
this.pipelineProvider.reset();
this.authService.clearIdentity();
this.router.navigate("/", { replace: true, trigger: false });
this.router.reset();
this.aurelia.setRoot("public/public/public");
}
Whilst it goes straight back to the public route I get an error in the console.
aurelia-logging-console.js:47 ERROR [app-router] Error: There was no router-view found in the view for ../components/clients/clientList/clientList.
at _loop (aurelia-router.js:281)
at NavigationInstruction._commitChanges (aurelia-router.js:307)
at CommitChangesStep.run (aurelia-router.js:143)
at next (aurelia-router.js:112)
at iterate (aurelia-router.js:1272)
at processActivatable (aurelia-router.js:1275)
at ActivateNextStep.run (aurelia-router.js:1161)
at next (aurelia-router.js:112)
at iterate (aurelia-router.js:1191)
at processDeactivatable (aurelia-router.js:1194)
I was clicking on the clientList nav link which does have a router-view..
How/where do I place the pipelineProvider.reset()? (if this is the problem)
But what I really want is...
How do I stop this router and cleanly move to the other router?
As you do I keep trying things.. This worked for me as I believe the pipelineProvider needed to finish before moving to the next step.. So I used a promise like so:
forceReturnToPublic(): Promise<any> {
return Promise.resolve()
.then(() => this.pipelineProvider.reset())
.then(() => this.authService.clearIdentity())
.then(() => this.router.navigate("/", { replace: true, trigger: false }))
.then(() => this.router.reset())
.then(() => this.aurelia.setRoot(PLATFORM.moduleName('public/public/public')));
}
...and no errors.