In Vue, when I want to restrict some pages for some users, in router I define a permission in meta, like this:
routes: [
{
path: 'transport',
name: 'transport',
component: Transport,
meta: { permission: "edit_transport" }
},
]
When user logins I have its JWT where I can find what permission he/she has and in route.beforeEach, I check if user has this permission, he/she can visit a page.
router.beforeEach((to, from, next) => {
let token = window.localStorage.getItem('user_token');
if(to.meta.permission == 'edit_transport') {
//if user's token has edit_tranport permission let him visit the page
}
})
But, now I have this permissions In database, there is a table where admin defines permission for page:
id permision page
_____________________________________
1 edit_transport transport
2 write_post create_post
This means, for example user needs edit_transport permission to visit transport page.
My solution is: first I have to take all this permissions from database and save them in vuex store, and then access this store data in route.beforeEach and check what permission page needs and then check if user has this permission
router.beforeEach((to, from, next) => {
let token = window.localStorage.getItem('user_token');
let pagepermission = store.permissions[to.name]
if (token.has(pagepermission)) {
// let user visit the page
} else {
// redirect
}
})
So, is it a good way or is there any other way better then this?
This is a good check to do on the client side, if you are also protecting routes / endpoints on the back end I think that would be the most well rounded solution.
I'm not sure what you're using on the backend but if its Node, you could use something like Passport to manage roles - as a middleware to routes and endpoints.
Related
I currently have a service authentication up and running (using jwt to auth).
I'm working on a quiz service that force user to create some required information and force them to take a quiz to understand how to use our tool.
Because lacking of Frontend exp, I'm wondering how this quiz service will integrate with the auth service
Right now, for Backend side during auth service I will give them back the permission in the token if I call the function to check if the user pass the test & have a profile created. Otherwise I give them back the token with permission = []
But for the Frontend side, what is the solution to re-direct use to Quiz page (after sign-in and what about user that already log-in before)
check this documentation programatic navigation
once authenticated redirect the user back using [vue2 code]
this.$router.push({name:'Quiz',params:{id:this.$route.query.next})
In the authentication page you may pass the quiz link as next query parameter eg
example.com/login?next=< quizID >
Navigation guard documentation
You can use route guards to redirect unauthenticated users to the login page, do remember to pass the quiz id as a query parameter.
your guard will be similar to this
router.beforeEach((to, from, next) => {
if (to.name === 'Quiz' && !isAuthenticated) next({ name: 'Login', query: { next: to.params.quizID }})
else next()
})
This was assuming you have set your routes has a route named Quiz and takes :id and a route named login, similar to this.
{
path: '/quiz/:id',
name: 'Quiz',
.....
},
{
path: '/login',
name: 'Login',
......
},
Alternatively, you could have set up a dialog box on the quiz page that handles authication.
I have a /profile route that should only be accessible by an authenticated user. From research, the recommended approach is to use vue-router's navigation guard:
Here is the route object:
{
path: '/profile',
name: 'MyProfile',
component: () => import('#/views/Profile.vue'),
meta: { requiresAuth: true }
},
And here is the router's navigation guard:
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (isAuthenticated()) {
next()
}
else {
alert('Auth required!')
next('/login')
}
}
})
The isAuthenticated() function above sends a jwt token (stored in cookie) to the /jwt/validate endpoint which validates the token and returns a 200 OK response:
export function isAuthenticated() {
axios.post(`${baseUrl}/token/validate`, {}, { withCredentials: true })
.then(resp => resp.statusText == 'OK')
.catch(err => false)
}
With this approach, every time we visit the /profile route, we are making a POST request to the /token/validate endpoint. And this works quite well. However, is it too excessive to make this request every time?
My Solutions
I wonder if there is some way to store the data locally in memory. I thought of two possible solutions:
Option 1: Storing the data on vuex store, however I have learned that the vuex store object is accessible via the browser's console. This means that anyone can modify the access logic to gain access to protected routes.
Option 2: Store it inside a custom Vue.prototype.$user object, however this is similarly accessible via console and therefore has the same security risk as option 1.
Essentially, my question is: is there an option to access a protected route without having to validate the jwt token on the server-side every time?
You should query the server for if the token is valid once when the user initially loads the application. After that, there is usually no reason to check again. If the session expires on the server, then any other API calls you do should return a 401 response (or any other way that you choose to return an error) and your application can act on that.and your application can act on that.
If your profile route is getting data from the server to display and the server is properly validating the user for that request, then it doesn't matter if the user tries to manipulate the Vuex store or Vue state because they won't be able to load the data.
Doing authentication in the vue router is really more for convenience than for actual security.
Don't waste time trying to prevent a malicious user from exploring the Vue application - that is guaranteed to be a losing battle since all of the code is loaded into the browser.
If you really insist on some kind of protection, you can split the application using webpack chunks that are loaded dynamically and configure your web server to only serve those chunks to properly authenticated and authorize users. That said, I would expect such configuration to be difficult and error prone, and I don't recommend it.
I'm using Passport-jwt with Express. I'd liket to restrict pages from logged-in user by middle-ware.
For example, I have a page, /dear-not-sign-in-user After a user logged-in, the user can't access the page. So basically I'd like to implement the opposite of passport.authenticate method. How can I detect the user's token is not valid?
router.get(
"/dear-not-sign-in-user",
!passport.authenticate("jwt", { session: false }),
(req, res) => {
console.log('this user is not logged-in yet');
}
);
Above code doesn't work, but you get the idea what I want to do.
I have some routes protected in vue-router, this I do through the meta property of the routes in vue-router.
When I load the index route, that is, all the records show me the edit button since at that moment I have this permission, if I update that user, I go to another panel with the admin user and I remove said permission. Now I go to the normal user and I give in the edit button of my table, I should send me to a route 403 since I do not have that permission at that moment, but it does not, just to the next navigation to another route I update the permissions. How do I solve this?
router.beforeEach((to, from, next) => {
store.dispatch('ME'); // get permissions before navigate route
if (to.matched.some(record => record.meta.permission_name)) {
// I check if the route permits, otherwise send to route 403
if(!store.getters.containsPermission(to.meta.permission_name)){
next({name : '403'})
}
else{
next()
}
}
else{
next()
}
})
The permissions are in the database from the backend I use Laravel and Laravel Permissions, by modifying the permissions for that user from another administrator user, and doing the store.dispatch('ME'); I get the new permissions, which I thought that by calling the dispatch before evaluating the meta, it would arrive correctly to the next({'403'});
I hope you understand me, if not, do not hesitate to comment.
Don't put permissions in your store. I, or someone else like me who is crafty, could just insert full permissions for your application whenever I wanted. Instead, query with the API for them before every route change.
router.beforeEach(async (to, from, next) => {
try {
await api.get('permissions', { params: permissions: to.matched.some((record) => record.meta.permission_name }})
next()
} catch (e) {
next({ name: '403 })
}
})
And just handle it server side.
I am logging in the system successfully but
I want that the user whose role is 'Administrator' can only go to all the routes and user having role as 'Manager' can go to 'Home' and 'GetDocumentDetails' else the other login users will be restricted to home page and the guest to Login page.
My route and filter files are as follows:
Routes:
Route::post('/login', function()
{
$user = array(
'username' => Input::get('username'),
'password' => Input::get('password'));
// verify user credentials
if (Auth::attempt($user,true))
{
$role= Auth::user()->userrole;
return Redirect::route('home');
}
}
// Route for getting document details using get method
Route::get('GetDocumentDetailsById',array('as'=>'GetDocumentDetailsById','uses'=>'DocumentController#GetDocumentDetailsById'));
// Route for guest user
Route::filter('guest', function()
{
if (Auth::check())
return Redirect::route('home')->with('flash_notice', 'You are already logged in!');
// Redirect Log-in user to his home page
});
Filters:
/* Filter to redirect guest user to login page */
Route::filter('auth', function()
{
$role=Auth::user();
if (Auth::guest()) return Redirect::guest('login');
});
Route::filter('auth.basic', function()
{
return Auth::basic('username');
});
Route::filter('guest', function()
{
if (Auth::check()) return Redirect::to('/');
});
I would recommend using Zizaco's Entrust and Confide packages for this purpose, as they greatly simplify this task.
Follow the tutorials on these pages after installing both packages, and then you can limit access to certain routes just by defining Route Permission filters or regular Route filters. No need for any additional logic in controller actions.
These Route Permission filters can easily be defined like this:
// Any route under admin is only accessible by users with role Admin.
// Redirect happens if user doesn't have this role.
Entrust::routeNeedsRole('admin*', 'Admin', Redirect::to('/'));
A Route filter based on permissions would look like this:
Route::filter('myFilter', function()
{
if (!Entrust::can('get_document_details') )
{
return Redirect::to('/');
}
});
This filter could then be applied to any route. See the Entrust documentation for more examples and features. Entrust is really versatile.