I'm creating a Single Page Application using Vue front-end, Express and Parse (parse-platform) for back-end. Whenever I authenticate user, I put user's info into session variable req.session.user = result; and then send it back to the client res.status(200).send(req.session);. Whenever user is routing through application, how do securely check if authentication is valid? What I am afraid of, is that the session id that is put into client's cookies could be forged and user would be treated as authenticated. I believe I could send a request to my back-end to check if authentication is valid every time user enters a route but I believe this is not a great idea as routing in vue applications are very quick and if hundreds of users navigating quickly could cause a problem. What else could I do? Or am I doing it/thinking of it the right way?
I use express-session to store client's session into his cookies.
app.use(session({
secret: 'secret_key',
resave: false,
saveUninitialized: true,
cookie: {} }));
This is how I login user:
Parse.User.logIn(username, password).then(result => {
req.session.user = result;
res.status(200).send(req.session);
});
first of all, I recommend using state rather than a session in a single page application.
vuex = https://vuex.vuejs.org/guide/
vue-router have a function called beforeEach.if we defined this function,it's called every time when we call a route. basically request go through this function.
then we can check this user is authenticated or not in this function
ex:-
let router = new Router({
mode: "hash", // https://router.vuejs.org/api/#mode
linkActiveClass: "active",
scrollBehavior: () => ({ y: 0 }),
routes: configRoutes(), // im using function to define all the routes. you can define routes here
});
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (localStorage.getItem("userToken") == null) {
next({
path: "/login",
params: { nextUrl: to.fullPath },
});
} else {
if (!store.state.isAuthenticated) {
next({
path: "/login",
params: { nextUrl: to.fullPath },
});
} else {
next();
}
}
} else {
next();
}
});
after that, we define which route should be authenticated or not. Vue router allows us to define a meta on our routes so we can specify the authenticated routes
ex:-
{
path: "/student",
name: "student",
component: Student,
meta: {
requiresAuth: true,
},
},
now everytime someone enter "/student" url it's gonna check if that user authenticated or not.
this is where I learned this
hope this will help someone.good luck
Related
I am attempting to build a Vue application that uses Supabase authentication. Inside one of the route guards in the router file, I implemented supabase.auth.getUser() in order to retrieve the user login status for a conditional that prevents next() from executing before the user is authenticated:
// Route guard for auth routes
router.beforeEach((to, from, next) => {
// const user = supabase.auth.user();
const { data: { user } } = await supabase.auth.getUser();
if (to.matched.some((res) => res.meta.auth)) {
if (user) {
next();
return;
}
next({ name: "Login" });
return;
}
next();
});
However, when I implement supabase.auth.getUser() inside the router guard, I get the following error in the console before logging in: "invalid claim: missing sub claim". After logging in, the error goes away. If I remove the supabase.auth.getUser conditional from the route guard, the error also goes away. After some additional digging online and running my Supabase anon key in jwt.io, it appears that my key is missing sub claim in the payload. If this is preventing the key from being authenticated properly, how can I resolve it?
You should be checking against the session rather than the user. The user will try to check against a JWT first which wouldn't exist at the time of checking since you're not logged in. Use getSession instead:
// Route guard for auth routes
router.beforeEach((to, from, next) => {
// const user = supabase.auth.user();
const { data: { session } } = await supabase.auth.getSession();
if (to.matched.some((res) => res.meta.auth)) {
if (session?.user) {
next();
return;
}
next({ name: "Login" });
return;
}
next();
});
I have a VueJS single-page application and I use JWT authentication.
I'm trying to figure out how to make sure that User is authenticated after page reload and if not, redirect them to login page.
accessToken and refreshToken are stored in the cookies and also in Vuex
Vuex.state:
auth: {
user: {},
isAuthenticated: false,
accessToken: null,
refreshToken: null
},
Vuex.actions.refreshToken
refreshToken: async ({state, commit, dispatch}) => {
try {
await api.jwtRefresh(state.auth.refreshToken).then(response => {
if (response.status === 200) {
dispatch("setAuthData",{
accessToken:response.data.access,
isAuthenticated:true
})
}
}).catch(err => {
dispatch('logout')
});
} catch (e) {
dispatch('logout')
}
},
App.vue
export default {
data: () => ({}),
mounted() {
this.$store.dispatch('setAuthDataFromCookies')
this.$store.dispatch('refreshToken') // checks if user is authenticated, redirect to login page if not
this.$router.push('/dashboard')
}
}
My idea is to try to refresh the JWT token. If it was successfully refreshed User can proceed to /dashboard. If not, User is redirected to the /login
The problem is that mounted doesn't wait until refreshToken is done and it redirects User immediately to the /dashboard even before token is refreshed.
How can I make it wait? (The idea is that refreshToken will redirect user to /login in case of error.
You can setup a meta auth field in your router, and a global beforeEnter or beforeEach guard that checks Vuex (or your cookies, or both) for a token.
In your router.js file you'd have something like
routes: [
{
name: 'Login'
},
{
name: 'Dashboard', // + path, component, etc
meta: {
auth: true
}
}
]
Then you setup a global guard, something like this:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.auth)) {
if (!store.getters.authToken) {
next({ name: 'Login' });
} else {
next();
}
} else {
next(); // Very important to call next() in this case!
}
})
This will check before each and every route transition whether the next route has the auth meta field. If it does, it checks your Vuex state for the token, and otherwise navigates as normally.
Vue Router Docs on Navigation Guards
In your case, you're trying to authenticate the user, so you can just call your endpoint inside of the beforeEach guard and redirect like that based on the response. Just make sure to make the callback asynchronous, like router.beforeEach(async (to, from, next) => {})
After a user has logged in the first time I redirect him to the page CreateProfile where he types in all the profile information. Afterwards I want to make this site not accessible anymore, such as if the user types the URL into the browser (e.g. www.myproject.com/createProfile).
How can I make sure that only the redirection from my login page has access to the CreateProfile page? That is, if the user types in the URL manually he will be redirected e.g. to the 404 page.
Currently my route for CreateProfile looks the following:
{
path: '/createprofile',
name: 'CreateProfile',
component: CreateProfile,
beforeEnter: (to, from, next) => {
if (store.getters.getUserStatus == true) {
next()
} else {
next({ name: 'Login' })
}
}
}
Thanks!!
You can check the from route object in the beforeEnter navigation guard to test the previous route location.
For example, check that the from.name property is Login, which will only be true when there has been the redirect you want. (Assuming you don't also provide a <router-link> from Login):
beforeEnter: (to, from, next) => {
const isRedirected = from.name === 'Login';
if (isRedirected && store.getters.getUserStatus == true) {
next()
} else {
next({ name: 'Login' })
}
}
I am trying to add a Webinterface to my NodeJS Discord-Bot and decided to use Express for this. Now I have succesfully managed to set things up an managed to connect the Web-Socket with the Bot and was able to send my first Message through the Websocket into a Discord-Channel. Now I want to create different Sites in the Webinterface with each different uses. These are supposed to be linked through a neat Navbar at the Side of the Page. Once clicked, the user should be redirected to the new site without losing the Token he is authenticated with. For the Purpose of Testing the Token is '123456'. The User is supposed to be redirected by clicking on this Button
(layout.hbs)
<form action = "redirectInfo">
<div class = "sidenav-element"><button type="submit">General Informations</button></div><br>
</form>
By clicking, the action "redirectInfo" is being triggered, which looks like this:
(webs.js)
this.app.get('/redirectInfo', (req, res) => {
res.redirect(301, 'infoSite')
})
I have tried using it both with the 301 and without which both leaves out the token .
This then redirects me to the 'infoSite' which is displayed using the following:
(webs.js)
this.app.get('/infoSite', (req, res) => {
var _token = req.query.token
if(!this.checkToken(_token)) {
res.render('error', { title: 'Error', errtype: 'The given Token is invalid.'})
return
}
res.render('infoSite', {
title: 'Webinterface',
token: _token
})
})
However this results in the infoSite telling me my Token is invalid, while the Default Page perfectly works with the Same Methods. This is the Code from the Default Page:
(webs.js)
this.app.get('/', (req, res) => {
var _token = req.query.token
if(!this.checkToken(_token)) {
res.render('error', { title: 'Error', errtype: 'The given Token is invalid.'})
return
}
var chans = []
this.client.guilds.cache.first().channels.cache
.filter(c => c.type == 'text')
.forEach(c => {
chans.push({ id: c.id, name: c.name})
})
res.render('index', {
title: 'Webinterface',
token: _token,
chans
})
})
In this Case "chans" can be ignored, as it's used to send Messages to Specific Channels in my Discord Server.
In both Cases _token is supposed to be Defined by The constructor and a function named checkToken (Code Attached)
constructor(token, port, client) {
this.token = token
this.token = token
this.client = client
this.app = express()
this.app.engine('hbs', hbs({
extname: 'hbs',
defaultLayout: 'layout',
layoutsDir: __dirname + '/layout'
}))
this.app.set('views', path.join(__dirname, 'views'))
this.app.set('view engine', 'hbs')
this.app.use(express.static(path.join(__dirname, 'public')))
this.app.use(bodyParser.urlencoded({ extended: false}))
this.app.use(bodyParser.json())
this.registerRoots()
this.server = this.app.listen(port, () => {
console.log(`Webinterface started on Port: ${this.server.address().port}`)
})
}
checkToken(_token) {
return (_token == this.token)
}
My Problem is, that whenever I leave the Default Page, the Token isn't being redirected with me and the Website tells me I've got an Invalid Token.
My Question is therefore, how can I redirect a client between multiple Sites, without him loosing the Token he is authenticated with. Thank you in Advance.
I am trying to redirect non-logged in user from all pages to /login. I tried beforeEach() but it doesn't fire when user enter site with direct url like /home, /event.
Per-Route Guard beforeEnter() works perfectly since it fires once the user lands on that particular page. However, it requires me to add beforeEnter() on every routes.
I am looking for a way to duplicate that beforeEnter() on almost every page on the router (even on dynamic pages) which non-logged in user will be redirected to /login.
This one works when user enter with direct url /home.
routes: [
{
path: '/home',
name: 'home',
beforeEnter(to, from, next){
if ( to.name !== 'login' && !this.isloggedin ){
next({
path: 'login',
replace: true
})
} else {
next()
}
}
},
...
]
This one only works after user entered the site and route changed
vm.$router.beforeEach((to, from, next)=>{
if ( to.name !== 'login' && !this.isloggedin ){
next({
path: 'login',
replace: true
})
} else {
next();
}
})
Thanks in advance.
It looks like this beforeEach is being defined inside an initialized component, which means the first routing has already occured. Define it in the router module with your routes instead:
const router = new VueRouter({
...
})
router.beforeEach((to, from, next)=>{
if ( to.name !== 'login' && !this.isloggedin ){
next({
path: 'login',
replace: true
})
} else {
next();
}
})
Hopefully you are using Vuex and can import the store for store.state.isloggedin. If not using Vuex yet, this illustrates why it is useful for global state.
For a global and neat solution, you can control the router behavior in the App.vue using the router.beforeResolve(async (to, from, next) => {});.
beforeResolve is better than beforeEach, as beforeResolve will not load the component of the accessed path URL unless you fire manually the next function.
This is very helpful as you'll not render any interafce unless you check the authentication status of the user and then call next().
Example:
router.beforeResolve(async (to, from, next) => {
// Check if the user is authenticated.
let isUserAuthenticated = await apiRequestCustomFunction();
// Redirect user to the login page if not authenticated.
if (!isUserAuthenticated) {
location.replace("https://example.com/signin");
return false;
}
// When next() is called, the router will load the component corresponding
// to the URL path.
next();
});
TIP: You can display a loader while you check if the user is authenticated or not and then take an action (redirect to sign in page or load the app normally).