Secure logging in to a Vue.js SPA - javascript

I'm pretty new to Vue.js, I followed lots of tutorials, but one thing bothers me.
Let's say I'm building a SPA. The user can log in and then access data using a RESTful API.
So the first step would be to send my login information (username, password) to the API and receive a token. The token would be saved to my vuex and also to the localstorage/cookie so the user stays logged in after a page refresh.
All of the other requests to the API would be signed by the token.
My routes are set up:
const routes = [
{
path: '/dashboard', name: 'dashboard', component: Dashboard, meta: { requiresAuth: true }
},
{
path: '/login', name: 'Login', component: Login,
},
]
I'm using a route guard to protect the /dashboard page from displaying to a non-logged in user:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.loggedIn) {
next({ path: "/login" })
} else {
next();
}
} else {
next();
}
})
My concern is with the loggedIn getter from vuex store which is implemented this way:
const getters = {
loggedIn(state) {
return state.token !== null;
}
};
Everything is working as it supposed to. I understand that without a access token I won't be able to get data from a server.
But...
I can open the developer tools, put an access_token to my localstorage with a random value, refresh the page and suddenly I can access the /dashboard page.
So my question is, how to avoid this scenario?
My first idea was, to have 2 pages - the login page and the second one which contains the SPA. The authentication would be done via session server-side and the SPA page could be accessed only to a logged in user.
Or is there some standard way to to this, so my SPA can handle this?

as said in the comments, i would create a checkLogin() like this:
checkLogin() {
axios
.get('/webapi/check')
.then(() => {})
.catch(err => {
if (err.response.status === 401) {
// use a mutation to toggle your "loggedIn" state
this.$store.commit('loggedIn', false)
if (this.$route.path !== '/login') {
this.$router.push('/login')
}
}
})
}
and there for u can use ur routeGuard to use the function on each change
and ofc ur backend should't allow valid backend responses without the token
UPDATED
u need a state for loggedIn
then u do a mutation to toggle this state depending on how your backend response.

Related

Redirect user if not logged in react js jwt node js

There are already many answers but I couldn't use them according to my scenario
in which my login component is a child component and i am using react-router-dom of 5.1.2 version
currently i am using local storage to save authToken but I know it is not a good approach. Where should I save the auth token then such that whenever someone tries to hit login route manually or by button they are redirected because token was expired
my route that i am currently using in app.js
<Route path={process.env.PUBLIC_URL + "/login-register"} component={(!isVerified)?LoginRegister:HomeFashion}/>
and this is the child component login
const login = async()=>{
try{
const options = {
username:username,
password:password
}
var response = await axios.post(CONFIG.url+'api/login',options)
localStorage.setItem("__user",JSON.stringify(response.data))
history.push({
pathname: '/',
// search: '?update=true', // query string
state: { // location state
user: response.data,
},
});
}catch(err){
isError(true)
}
}
React.useEffect(()=>{
if(location.state!==undefined){
setMsg(location.state.msg)
if(msg)
{
alert(msg)
}
}
},[location])
what i am doing is when i am successfully logged in i get token and user data in response form backend I store it in localStorage and redirects things according to it
what is the right way to do so?

How to prevent users who are not logged in from accessing the admin route?

In my application the users don't have accounts, so by default every registered account is an admin account. When an admin logs in, he receives a valid token with which he can make POST request to the back-end and create/update/delete content.
Here's my problem - even though non-admins can't create/read/update/delete content through the admin panel because they don't have a valid token and the back-end rejects their POST requests, they can still visit the admin panel by navigating to /admin.
I can check if the user is logged in by checking if the Vuex user object is empty or not, and redirect the user to home if he's not logged in, but I'm pretty if the user desires it, he can spoof being logged in ( the token would still be invalid ).
What is the best course of action here?
The best practice is use vue-session-npm and make a session after admin is logged in successfully. Then you can check if session is present or not . If present give permission to use /admin router or redirect the user to login page. To know more browse session and cookies uses.
You can register a global before on your router which will be executed on every call to you can check the user permission.
Details:
https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
router.beforeEach((to, from, next) => {
// ...
})
To create a more structured way, you can add meta data on your router.js
You can add the user ROLE to your TOKEN
{
path: '/admin/path',
meta: { requiresAuth: true, roles:[ADMIN, MANAGER] }
}
ADMIN, MANAGER can be Strings, Number, it's up to your logic.
So the beforeEach would be like:
router.beforeEach((to,from,next) => {
//get the authenticated user
const authUser = store.getters.getUser
//check sensitive routes
if (to.meta.requiresAuth) {
if (!authUser) {
next('/login')
} else {
//check user ROLE/Permission
if (hasPermission(authUser, to.meta.roles)) {
next()
} else {
next('/unauthorized')
}
}
} else {
next();
}
})
The hasPermission function only checks if the authenticated user has a ROLE which allows him to access the Path.

Sapper session not setting properly in production build without page reload

I'm working on a project using Sapper, and been struggling with something for a bit now – can't quite figure it out. I'm using Polka for my server (it was included with the sveltejs/sapper-template#rollup template so I went with it), and cookie-session for my session middleware.
Here is my Polka configuration, where I seed session data also:
polka()
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
cookieSession({
name: 'session',
keys: [
'6e818055-d346-4fcb-bf56-4c7d54cb04ab',
'60f3e980-6e9c-460d-8ea7-af1fffbdb92f'
]
}),
sapper.middleware({
session: (req, res) => ({
user: req.session.user
})
})
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
When a user logs in, it's handled in routes/login.js essentially some profile info and tokens get set on session.user and the request is redirected back to the root path. I'm using #polka/send-type to respond. See below:
req.session.user = {
accessToken: access_token,
name: first_name,
pic: picture.data.url
};
send(res, 302, '', {
Location: appUrl
});
There, I have a preload module for routes/index.svelte checks if session.user exists, and if so redirects to dashboard:
<script context="module">
export async function preload(page, session) {
const { user } = session;
if (user) {
return this.redirect(302, "dashboard");
}
}
</script>
This all works fine in dev, but when I npm run build and build for production it doesn't so well. It seems like the session.user isn't getting populated after the redirect in login.js.
The session object that I get in preload doesn't have session.user set after login, and vice versa on logout where session.user is simply set to null and the client is redirected to the root, session.user is still populated.
If I refresh the browser, session.user is in the correct state. Without a refresh – if I just logged out I can click around as if I were logged in, and if I had just logged in nothing happens as session.user is undefined.
Just not sure why it'd work on the dev build but not in production. Given that the session.user state is correct on browser refresh, I'm guessing it's not an issue with the cookie middleware configuration. Seems like I'm missing something else. Any help would be hugely appreciated!
Sapper does indeed handle the cache headers differently for dev and production environment. That's why you are experiencing it this way.
But there is a simple solution by changing the sapper.middleware function:
sapper.middleware({
session: (req, res) => {
res.setHeader('cache-control', 'no-cache, no-store')
return { user: req.session.user }
}
})
This sets the cache-control as you want it.

React + Passport.js

TL;DR I'm making a MERN app and want to access the passport user from React. How?
I'm trying to make a simple to-do app with the MERN stack and can't get user auth down. I'm using React on the front end with a proxy to an express api. I want to grab the req.user object from React so I can update the navbar based on whether the user is logged in or not. When I post to /api/login I can log the user object from the api route.
React login form:
class Login extends Component {
//..
async login(event) {
event.preventDefault();
await api.login({
username: this.state.username,
password: this.state.password
});
this.setState({
username: '',
password: ''
});
}
//..
}
Axios:
export async function login({username, password}) {
axios.post('/api/login', {username, password});
}
Server:
router.post('/login', passport.authenticate('local'), helpers.login);
exports.login = (req, res) => {
res.json({ message: 'You have been logged in!' });
};
From this exports.login function I can console.log(req.user) but when I try to access the user from any other route it is undefined.
Here's what I want to do:
React:
class App extends Component {
//..
componentDidMount() {
this.isLoggedIn();
}
componentDidUpdate() {
this.isLoggedIn();
}
async isLoggedIn() {
const user = await api.isLoggedIn();
}
//..
}
Which will make a request that returns the user object.
I somewhat understand how the proxy may get in the way and how the session works but the pieces haven't clicked yet. Hoping someone can help me out.
EDIT So um... the problem just kinda went away. I'll update if something breaks again.
Possible duplicate here But to check out the accepted answer by #Molda there was a link to the Github Repository that gave a very good example of how to handle the authentication on the client. You will have to change some of the token authentication to the user that is returned from the server when you login.
Basically you need to store the user somewhere on the client like the browsers local storage.

Redux middleware for dynamic auth / redirecting

I attempted to create a very simple redux middleware to handle the auth needs in my application. Users in this app can have 3 states:
unregistered (should always be redirected to login)
partially registered (should redirect them to a secondary login. Users need to belong to a group to access any features in the app. So after initial registration they need to create or join a group. OR if they leave their group they'll be in this state and will need to have any actions redirect them to this page until they join a new group)
fully registered, should have full access with no forced redirects until they log out or leave their group.
So I was thinking a simple solution would be to have middleware that checks the status code of any async actions and redirects accordingly. So maybe a 401 would trigger a redirect to to the login/signup page, /login, and a 422 would trigger redirect to the secondary registration, /register.
This is my basic set up so far:
import { browserHistory } from 'react-router'
export default function({ dispatch }) {
return next => action => {
const url = window.location.href.split('/')
const location = url[url.length - 1]
if (location === 'login' || location === 'registration') {
return next(action)
}
let status;
const token = localStorage.getItem('token')
if (action.payload) {
status = action.payload.status
}
if (status === 401) {
return browserHistory.push('/login')
} else if (status === 422) {
console.log('redirect to registration')
return browserHistory.push('/register')
}
return next(action)
}
}
Obviously this approach does not utilize any higher order components for auth like most react/redux auth examples use. It seems to work OK, but I see a view start to render for a split second before a redirect so it isn't 100% smooth.
Is there a more efficient / simple way to handle this three state auth system? I am using react-router-redux.

Categories