const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/main",
element: (
<AdminAuth redirectTo="/profile">
<Main />
</AdminAuth>
),
},
])
import React from "react";
import { Link, Navigate } from "react-router-dom";
import { isAutheticated } from "../auth";
export const AdminAuth = ({ children, redirectTo }) => {
let auth = isAutheticated().user_Role;
return auth === "admin" ? children : <Navigate to={redirectTo} />;
};
I want to prevent routing while user manually changes url in the browser. I have the "/main" as an admin route, which I'm protecting, but the issue starts when user changes his role in the local storage and tries to access `"/main". I want to prevent user from manually changing the route in their url or show error if they change manually.
EDIT: I'm protecting my route in the backend, but in the frontend I don't user to even access this.
Considering that all code on the client can be changed by the user, setting up protected routes like yours, should only be for a better user experience. You cannot and should not depend on client logic to protect data from non-admin users. This has to be done on the backend/server.
Therefore, using local storage as your business logic for identifying an admin user or not, would be highly critical, since everyone could change that. Instead it should be done by some token, for exmaple JWT (Json Web Token) using a authentication provider like AWS Cognito or similiar, or even building your own server side authetication logic.
Simply by sending prop from previous page, you can control and simply send the user back to the proper page.
I want to prevent user from manually changing the route in their url
or show error if they change manually.
There's simply just no way of knowing from inside the app how exactly the URL changed in the address bar. When a user does this the browser completely reloads the page, so the entire React app is mounted fresh. react-router can then only read what the current URL is to handle route matching and rendering.
If you are certain that you don't trust the frontend client then a solution here would be to validate the user against the backend on every route/component you want to protect each time they are navigated to.
Example implementation:
import React from "react";
import { Navigate } from "react-router-dom";
import { authService } from "../auth";
export const AdminAuth = ({ children, redirectTo, role }) => {
const [userRole, setUserRole] = React.useState();
React.useEffect(() => {
authService.validateUser()
.then(user => {
setUserRole(user.user_Role);
});
}, []);
if (userRole === undefined) {
return null; // or loading indicator/spinner/etc
}
return userRole === role
? children
: <Navigate to={redirectTo} replace />;
};
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/main",
element: (
<AdminAuth redirectTo="/profile" role="admin">
<Main />
</AdminAuth>
),
},
]);
Note that checking with the backend for each and every route transition will incur a network cost and slow the frontend UI page transitions down. Depending on the sensitivity of the content this may be an acceptable cost. That's a decision only you or your product owners can make.
Related
I'm building an app that has a page which ends in '#' provides some meta info for the page without '#', for example if the page '/user/aabb' has info about the user 'aabb', then the page '/user/aabb#' is the meta page for that user.
The problem is, '/aabb' part doesn't really exist because the app is SPA. 'aabb' is simply delivered as a prop for the component used in '/user' routing. Nor I can directly access '/user/aabb#' in the same context.
So is there a way for Flask to render a specific page of a Vue-build app? so that if the user enters '/user/aabb' on the address bar it links into '/user' page with 'aabb' prop. If there is, I guess the following functionalities should be required.
Flask to redirect to a specific page inside of Vue-route.
Flask to send data to the vue-component of that page.
Vue to receive the data from Flask.
Or is there any other ways to solve this... issue?
Thanks in advance.
The solution to all your questions is to use Vue Router with HTML5 History Mode.
As I mentioned in your last question, set up your Flask app to use the Vue SPA as the front-end
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return app.send_static_file("index.html")
Then set up a router for your front-end URLs
// router.js
import Router from "vue-router"
import Vue from "vue"
Vue.use(Router)
export default new Router({
base: "/", // this should match the root path for your app
mode: "history",
routes: [{
name: "UserMeta",
path: "/user/:username#",
component: () => import("./path/to/UserMeta.vue"),
props: true
}, {
name: "User",
path: "/user/:username",
component: () => import("./path/to/User.vue"),
props: true
}]
})
You have to make the #-suffixed meta routes are listed before the normal pages in order to guarantee it doesn't think the username ends in #. See Matching Priority.
In the example above, both components receive the username route parameter as a prop.
You can then use one of the Data Fetching methods to load data into your components from your Flask API when your routes are loaded.
For example, using Fetching After Navigation and assuming you have a Flask app route for /api/user/<username>...
<template>
<div>
<div v-if="user">
<!-- show user details here -->
</div>
<div v-else>Loading...</div>
<//div>
</template>
<script>
export default {
name: "User",
props: { username: String },
data: () => ({ user: null }),
async created () {
const res = await fetch(`/api/user/${encodeURIComponent(this.username)}`)
this.user = await res.json()
}
}
</script>
We are working in a project which is written in dotnet with Razor views (Pure Backend stack). Our plan to move every view to React using react-router-dom to handle routing in the client and server.
What we have in mind is to move page by page not to do a big bang because the project is already in production.
When I setup react-router-dom looks like every page is handled on the client and there is no full page reload if the route is not within the routes that Router handles.
Client Router
import { Router } from 'react-router-dom'
import { createBrowserHistory, History } from 'history'
const history: History = createBrowserHistory({ basename: baseUrl })
<Router history={history}>
...(routes and page layout)
</Router>
Server Router
<StaticRouter
basename={basename}
context={routerContext}
location={params.location.path}
>
...(routes and page layout)
</StaticRouter>
Is there any way to make the router work in this way:
If the route is within the ones handled by react-router-dom, then do client side transition
If the router is not within the ones handled by react-router-dom (which means it's still handled by dotnet backend), make a full page reload.
Let me know if you need more information.
Thank you in advance.
You need to add a NoMatch component for * route at the end of your Router
<Route component={NoMatch} />
and define your NoMatch component like below
function NoMatch() {
window.location.reload();
return null;
}
Also, your routes should be within a <Switch> because otherwise NoMatch will execute as well as your route and this will cause and endless loop.
See documentation for more details: https://reacttraining.com/react-router/web/api/Switch
Luckily we have history prop to help, which provides an action param that can indicate whether the user navigated from another route or started on the current route directly.
So this is how I solved the reload-loop issue:
/**
* Handles Legacy/SSR pages, reloads the page when the location changes
*
* #param {object} props Props
* #param {object} props.location Location object
* #param {object} props.history History object
*/
const LegacyRoute = ( { location, history } ) => {
useEffect( () => {
// Reload the page if the location change ( and not on first load )
if ( history.action === 'PUSH' ) {
window.location.reload();
}
return () => {
// Reload the page if we navigate away to another route
window.location.reload();
}
}, [ location.pathname ] );
return null;
}
Then you define the route as:
<Route component={ LegacyRoute } />
I want to render dynamic next.js pages with custom content / style based on the domain which requests the page.
Basically run one next.js app under multiple domains.
I know that I have to do some sort of custom routing, but don't know exactly how and how I can pass the host information to the requested page, so it fetches the matching data from a database.
You should be able to verify this in your pages/_app.jsx file static getInitialProps(context) method and use the context to verify where the request is coming from then return a flag to the component.
Example:
// pages/_app.js
import app from 'next/app';
export default class MyApp extends app {
static getInitialProps({ Component, router, ctx }) {
let pageProps = app.getInitialProps({ Component, router, ctx });
if (ctx.req.headers.host.match(/localhost/)) {
pageProps = {
...pageProps,
renderFrom: 'mydomain',
}
}
return { pageProps };
}
render() {
const { Component, pageProps } = this.props;
return (
<section id="app">
<Component {...pageProps} />
</section>
);
}
}
Note that I call app.getInitialProps to mimic the next/app component behaviour as in the source code here
In your pages/index.js you will have access to props.renderFrom and there you can display data.
// pages/index.js
import React from 'react'
const Home = props => (
<div>
Rendering content for domain: {props.renderFrom}
</div>
)
export default Home
Since you're verifying where the request comes from in _app.js this property will be available in all your containers (pages) so you can have the same routing for your application and just render data differently.
Tip: You could also use next-routes to manage the routing for the application, it's better than the out next comes with, and in here you have a custom server where you can manage your traffic as well.
Hopefully this helps you and points you in the right direction.
Here’s an example of hosting multiple domains on the same Next.js site (while maintaining multiple languages and static site generation/SSG), using Next.js’ i18n system:
https://github.com/tomsoderlund/nextjs-multi-domain-locale
I have seen there are many answers for this question. But here, my question and my existing code is different. I'm using following NPM module to route in my react application.
import { Route, Switch, BrowserRouter } from 'react-router-dom';
And this is the routing file of my application.
https://gist.github.com/chanakaDe/241daafbc94df8543bced3695c7b7169
I want to use role base authentication system on this. All the roles are saved in the local storage and i need a way to check those when use goes to a route. I'm new to react. Please help me how to solve this.
Do I need to use separate file to get all the roles from local storage or can we do that inside the routing file ? Please advice
You can make Route custom components to handle the type of route. For example, if you have two Routes:
1) Admin Routes
2) Host Routes
then you will make one Component that will take the role as props and then using this component you will return the which route you need to render.
Small example is shown below:
class ResolveRoute extends React.Component {
render () {
const role = this.props.role
return (
{
() => {
if (roles === "admin") {
<Route ...this.props />
}
else if (roles === "host") {
<Route ...this.props/> // Render the routes that have host roles.
}
else {
<Redirect to="/" /> // or Not found error page.
}
}
}
)
}
}
I am trying to redirect a user clicking logout link to the server rendered logout page. But as it stands with code below I just get redirected to the default path.
I have a server route set-up in flask as the following:
#app.route('/logout')
def logout():
logout_user()
return render_template('login.html')
In vue I want to redirect the route so that I am directed to the server route.
<template>
<a v-on:click="logout">Logout</a>
</template>
<script>
export default {
name: SomePage.vue
},
methods: {
logout () {
this.$router.replace('/logout')
// I also tried .go('/logout')
}
}
</script>
Then do I need to set up a route in my router?
export default new Router {
routes: {
{
path: '/'
component: Default
children: [
{
path: '/logout'
// no component added here - is it needed?
}
]
}
}
As I have already redirected to /logout, I am not sure how adding a component for this route would help as I just want to go to the base /logout and get the .html page returned by the server. Any ideas?
I have some problem and find solution ...
try this in vuejs 2 +
this.$router.push('/logout')
Manipulating the window location via $router methods doesn't cause the browser to perform a full page reload, which is what you want in this case. You'll need to set the window location manually:
window.location.replace('/logout')