how to keep my store if i refresh a web page ?
let store = new Vuex.Store( {
modules: {
demo, auth
},
strict: true
} );
let router = new VueRouter( {
mode: 'history',
saveScrollPosition: true,
routes: routes
} )
I use the history mode but If i reload my webpage, my store is empty.
There is a solution ?
To persist state data after app refresh, you can either use localStorage or sessionStorage.
XSS: The problems with storing tokens in localStorage as you say is about the app being vulnerable to XSS (cross-site-scripting) attacks. Apart from that, it is quite safe. The localStorage is bound to the domain, so the data of www.yourapp.com will stay there even after the browser is closed also unlike cookies, you won't have to manage which site made the request. All the tabs on the browser with the same domain will be able to use the data in the localStorage
If the said behaviour is not needed, you can go for sessionStorage, it works almost the same way but the data gets cleared when the browser is closed.
Going for cookies, sure they will help saving your token being taken away by an XSS but then you will have to ensure you also provide a csrf-token to protect against CSRF (Cross Site Request Forgery) attacks.
If you plan on going forward with cookies, then make sure they have the httpOnly flag set to true in the headers, else they are no good.
Overall I have seen that localStorage has been a very commonly recommended solution for maintaining tokens and other app data.
I'll add sources to this answer for reference.
CSRF Token necessary when using Stateless(= Sessionless) Authentication?
Check the accepted answer and others as well.
https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTML5_Security_Cheat_Sheet.md
https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage
https://www.whitehatsec.com/blog/web-storage-security/ This would let you know about the pitfalls and how-to for using localStorage.
If you refresh the page (F5) you are re-running your app, so unless you initialize the store with some data, it will get empty as it is when app starts.
You can decide what states you want to keep in this case and save them into the cookie/LocalStorage and then in your app.js load them from cookie/LocalStorage into the store. It would be common practice for things like auth token etc as you want to keep user logged in in case of page refresh.
Nice post from Stormpath about storing the tokens: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage
If Vuex store and whole app is being reloaded when you are navigating from route to route - there is something wrong with your application, probably you are not using VueRouter navigation properly.
You can cache your state to the sessionStorage, which will survive page(tab) refresh events, but will be cleaned when the page or browser is closed.
Docs about sessionStorage
Related
I'm making an online quiz web application with Vue js, and .NET Web API. I have JSON Web Token Auth and pass the token in local storage, also I get all my questions and answers from my API and use vuex-persistedstate to keep my state when refreshing and switching routes. I store my state in the sessionStorage but realized that this is not a good idea since I have my answers and auth token there and anyone could just go to sessionStorage and see everything. Is there an alternative place where I can store my persisted state so it is not accessible by the public users of the quiz application?
Not really, if you store it on the front end, you will have it exposed at some point.
The JWT token is fine since it's meant to be public anyway (and not dangerous by itself).
As for the possible state that you do have there, you may consider fetching it only when needed.
I mean, if it's some kind of quiz thing where people need to guess it (and you don't want them to find that one out).
If it's your "regular data" that is available after a successful auth, then keep it there, no specific issues with it. You need to have it at some point and it will not get hacked under normal conditions.
Using Nuxt 2.15.3, with a Rails backend.
I'm trying to create a Google OAuth workflow in my app, and I'm having some trouble with the steps after getting the access code. Once the user authenticates with Google and gets redirected back with the access code in the URL params, I send a request to my own backend to save the access/refresh tokens in the User model.
NOTE: this google Auth flow is separate from my normal app sign-in/sign-up flow. I'm only using some Google APIs, so this has nothing to do with making a user account via Google OAuth, I'm only requesting access to some APIs in the user's Google account, namely the My Business API.
Now, my backend has a boolean google_authenticated field on the User table which gets set to true if the access/refresh token exists, which automatically gets sent to Nuxt as $auth.user.google_authenticated. This feature works fine, but my problem is that the page where the users get redirected to has a v-if that checks for this google_authenticated flag.
The template looks like this, obviously simplified for the sake of the question
<template>
<div v-if="googleAuthenticated">...</div>
<div v-else><a href="googleapis.com">Authenticate</button></div>
</template>
export default {
data() {
return {
googleAuthenticated: this.$auth.user.googleAuthorized,
};
},
async mounted() {
const accessCode = this.$route.query.code;
await this.$axios
.post("/users/google_oauth", {
access_code: accessCode,
})
.then((response) => {
this.$auth.fetchUser();
});
}
}
So as you can see, what I'm trying to do is to refresh the $auth.user.googleAuthorized automatically on mount when the user gets to the page with the code in the URL params. The problem is that $auth.user doesn't seem to be reactive, and the user needs to either navigate to another page or refresh the current page to get these changes to show up and for the v-if to trigger and to show the other div.
I can see from the dev console that the fetchUser() method does indeed get called, and I can see from the Vuex store that the auth/SET function has been called and that the $auth.user.googleAuthorized flag is set to true as well.
According to the Nuxt Docs, the $auth module is reactive, but I'm failing to see it. What can I do here to make these changes stick properly?
So it turns out it's a race condition (sorta).
What's happening is that once I tell my own API to get the access tokens from Google, that request obviously takes some non-zero amount of time. As such, Vue is waiting to get the 200 from my own API indicating that I've fetched the access token and can continue with using Google APIs.
So basically, by the time Nuxt is calling fetchUser() again, it's actually getting the user without the googleAuthorized flag set to true, because that just happens a tiny bit too slowly. It's not really something noticeable when you're just looking at dev tools though, since everything seems to be happening instantaneously more or less, but the timings are extremely close, which is the reason why it would sometimes work, and other times it wouldn't without a refresh.
I am managing performing a login and the login state using Vuex. It is a standard procedure of:
User submits their login details on a form
The form dispatches a Vuex action to make an axios API call to a Node/Express endpoint /api/login with the credentials
The response from the API if successful will send back the user's data which is stored in the state.user object through a setUser mutation. A flag of state.user.isLoggedIn is also set to true.
A JWT access token is sent to the user's browser and stored in a secure cookie to use over and over again until it expires. If it expires, a refresh token is used to generate a new JWT access token.
The site's header displays the user's login information .e.g name
The above all works as expected. However if the user refreshes their browser, then Vuex state gets emptied and the site header no longer shows the user information. To overcome this I stored state.user into localStorage in the browser as persisted data and repopulate the Vuex store using the data in localStorage on page refresh. If the user logs out, the localStorage is cleared.
There are many routes that require checking if the user is logged in. I cannot rely on localStorage because it can be manipulated by the user. If the user deletes their cookies which contains a JWT, then localStorage is not being emptied and the site header still displays their log-in information. Therefore I need to do an API call on almost every page to check if the user is logged in and if not then to delete the localStorage as well.
My question is:
Where do I perform a log-in check every time a page/view is accessed? Is it whenever the Vue app is mounted or within vue-router using the beforeEach navigation guard?
If I use beforeEach in vue-router, is it going to crash the site by making an API call to /api/login every time a user changes route? Is it normal practice to do this on every route?
Are there any other alternative patterns to keeping track of if the user is logged in or not?
Application decisions like this tend to be subjective because every application is different, with different requirements and different architectures. It sounds like this is a single page application, so I'll go from there.
First, the fact that your JWT is set up and working is great - that can be a lot of hard work, so be proud that you made it that far.
If your application has certain pages that are accessible without first being logged in (like a login page, or an access denied page, etc.), then you have to consider that in the design as well. In this case, a beforeEach route guard is the perfect solution to keep a route from loading or to catch those not-logged-in users before they attempt to request content that will likely just give them an error.
You probably shouldn't have to make an API call to the server each time the user navigates to a new page. That would probably be a little slow. Because your application uses JWT, complete with refresh tokens, you should be able to rely on them, so that if a user is logged in, they will continue to be logged in until they close their browser and walk away.
This is why you should not use localStorage. When a tab closes or even after a reboot, the user will still appear to be logged in. Instead of using localStorage, use sessionStorage - Helpful MDN link.
Now, the login event to retrieve the user object should only happen once per browser tab, but stay active during the visit.
I wonder why you don't just request a new user object from the server when the browser is refreshed. It's not strange to force a re-check to the server for a full page load. Doing that would mean you wouldn't have to use anything outside of Vuex to store the user's state
Next, is there a way to check the validity of their JWT during the navigation event? Depending on the way you handle the JWT, you may have access to it, and can possibly decode it (like through the oidc-client-js library), and that way determine for yourself if the token is expired. However, if the token is expired, your token refresh system may just refresh it and not tell you about it.
You can also watch your HTTP requests and look out for 401 or 403 responses (however your back-end handles logged-out users), and direct the user back to the login screen if you see one of those. You can use Axios' global interceptors to catch them, or do it yourself if you centralized the location from where Axios is called.
Overall, you're on your way, and clearly have a good grasp on this. Great progress so far, having done probably 90% of the heavy lifting already.
I'm working on a web app using Vue.js/Vuex and when the user refreshes the page the state is lost.
I'm trying to persist some states in sessionStorage, but I realized that a user can edit it and for example make a state true and turn it into false.
My question(s) are:
Can a user edit the sessionStorage?
Is it safe to store a state in a sessionStorage?
Can I persist the state in another way without adding another dependency on the project?
--------------UPDATE-----------------
For people with a similar problem I solved using beforeEnter and beforeEach so instead of trying to persist the state I go to a backend endpoint everytime the route changes (with beforeEach) and every page refresh (with beforeEnter).
Thanks to everyone for the helpful responses!
I guess your concern is about saving credentials in the store to sessionStorage. Storing tokens should be fine as long as you have robust authentication/validation logic at the backend. Don't store password though.
I've been using the localStorage to store the JWT token and do the authentication, it works fine because the backend is protected, what is not, are the front end pages in Vue.js and its components, anyone can create a false token in the browser and access the pages, the panel menu, but the data is not loaded because the token is invalidated by the backend. Did you realize that the user will be able to navigate the pages normally? How can I avoid this? Only allowing back-end validated users to navigate the site.
I'm using Vuex and I can not think of a solution. The isLogged state receives the localStorage token, so the user can easily circumvent the browser.
I thought of a solution, do not judge me. Before entering each route, send a request to the back end to check the token, if false, remove the localStorage, would this be a good one?
I'd say your solution is a pretty decent one to start with, but I think you can improve it a bit.
Because you're using VueX, what you could do is store the user (or some other variable) in the Vuex Store. Then before each route you can check if the Store has a user.
If the Store doesn't have a user, check if the localStorage has a token. If there is no token, then the user is not authorised to go to the route.
If the localStorage does have a token, call the back-end and check if the token is valid. If the token is valid, store the user in the Vuex Store and continue. If the token is not valid, than the user is not authorised to go to the route.
This will prevent you from creating unnecessary calls to the back-end whilst still having the validation.
Or you could for example in your store state have a user object that has something like 'userRole' this way you could check for Authentication/Token/Role.