How do I implement protected client routes? - javascript

Hey there! :)
I am using Next.js to build a college dashboard that incorporates Teacher, Student and Admin dashboards.
I have created a common Home page from where you can choose to login/signup as any of those. Since we have 3 different type of clients so we have these multiple dynamic routes for Authentication
What I want ?
I want the actual dashboard routes to be well protected if the client is not Authenticated i.e. the client should not be able to access any of those routes if he/she is not Logged in.
Also, If they did try to do the same they should get redirected to the Home page from where they can choose to further login/signup as a Teacher, Student or Admin.
What is the issue ?
I have a user object (it is null if no user is loggen in) using which I check whether a person is authenticated or not. So in my custom _app I am checking the same but when the user is not logged in I can redirect it to only a specific route say Home '/' which means if a person opted for say login as student he/she will again be redirected to the Home page which should not happen instead all the authentication related routes should be accessible.
import React from 'react';
import Home from './index';
import Head from 'next/head';
import Layout from "../Components/Authentication/Layout";
import Layout2 from '../Components/SD_Comps/Layout';
import Layout3 from '../Components/EP_Comps/Layout';
import { useUser } from '../lib/hooks';
import { useRouter } from 'next/router';
import '../style.scss';
import '../node_modules/hover.css/css/hover-min.css';
export default function MyApp({ Component, pageProps }) {
const router = useRouter();
const [user] = useUser();
return (
<>
<Head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"></link>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"></link>
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"></link>
</Head>
{user ? (router.pathname.startsWith('/StudentDash') ? <Layout2><Component {...pageProps} /></Layout2> :
(router.pathname.startsWith('/ExamPortal/') ? <Layout3><Component {...pageProps} /></Layout3> :
<Layout><Component {...pageProps} /></Layout>)) : <Layout><Home /> </Layout>}
</>
)
}
The Big Question !
How can I make specific routes accessible while keeping others protected when user is not Authenticated ?

In your current implementation you're sending all components to the client and you decide which component to show based on the user object. There's a risk that if somebody edited the user object manually, then they would gain access to unauthorized routes.
The only way to be secure is to authenticate users on the server level and always make sure the server holds the authentication logic, not the client. I'd suggest that you implement separate pages for each type of user: /student.js, /teacher.js and /admin.js and redirect users to appropriate page based on the login result. Then, test on the server if the user has the authorization to view a particular route. This could be done in getServerSideProps(), e.g:
/pages/student.js
export default function Student({ redirect }) {
if (redirect) {
return null;
}
return (
<>
<!-- render your page -->
</>
);
}
export async function getServerSideProps({ req, res }) {
/* Check the user status depending on your auth strategy */
const user = req.session.get('user');
/* Redirect to home route if user is not authenticated, or is not a 'student' type */
if (!user || user.type !== 'student') {
res.setHeader('Location', '/');
res.statusCode = 302;
return { props: { redirect: true } };
}
/* Proceed if authentication is successful */
return { props: { } };
}

Related

Problem with iFrame in React rendering another app (flutter app)

I have a React app that is using an iFrame to render another app that was made in flutter (see the first image):
The flutter app is hosted in a certain domain (so it's like a micro frontend). The app in React is the dashboard and is hosted somewhere else (different than the flutter app)
My problem is that when testing the flutter app directly in the hosted URL, it works as expected. When you click on the name of one person, a sidebar opens with some information and a button "Gestion oferta".
When you click on the button, it should take you to this other view:
So this works as expected if I test the flutter app directly in the URL where it is hosted, but when I click on that button inside the react dashboard, it does not behave as expected, it just shows another instance of the same react app (dashboard) inside the iFrame, like this:
Here is my code for this component in the react app that renders the iFrame, in which I call the URL for the flutter app:
import { Fragment } from "react";
import { css } from '#emotion/react'
import Head from "next/head";
import DashboardLayout from "../../../layouts/DashboardLayout";
import { getTenantByCompanySiap } from "../../../helpers/tentant";
import { UAT, PROD, getEnv } from "../../../helpers/env";
export { getSSProps as getServerSideProps } from '../../../lib/Page'
export default function NuevaSolicitudPage(props) {
const tenant = getTenantByCompanySiap(props.infoRh?.codeCompanySIAP)
const branch = props.infoRh?.codeBranch
const user = props.employeeData?.email
const getCampanas = () => {
const env = getEnv();
const url = {
[UAT]: `https://url-for-testing`,
[PROD]: `https://other-url-for-production`
};
return url[env] || url[UAT];
};
const url = getCampanas()
return (
<Fragment>
<Head>
<title>Gestión de cartera | Campañas</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<DashboardLayout
{...props}
title="Campanas"
>
<iframe
src={url}
css={css`
width: 100%;
height: 100%;
`}
frameBorder="0"
/>
</DashboardLayout>
</Fragment>
);
}
I do not have access to the flutter app code, I only consume it and show it in the iFrame, but I heard from someone that I need to configure some files in order to display flutter apps in an iFrame in react, but he is also not sure. I have searched for something like this but could not find anything relevant to this problem because the app is showing, it just does not behave as expected.
Can somebody give me an advice on how to solve this issue? Thanks in advance.
This issue had to do with the cookies, somehow the cookie to store the user session got lost/erased, so whenever you have something similar and you use cookies for user sessions, check if they are stored and used properly.

stop redirecting to the landing page with react route and auth0

I am trying to access authorised route(localhost:3000/abc) via non authenticated user. Then it will be redirecting to auth0 login screen successfully. But after I logged in via auth0, first it will redirect to the landing page with a query param(localhost:3000/code=f23set...) and then it goes to the authorised route(localhost:3000/abc) which I need. So how can I prevent from redirecting to the landing page(localhost:3000/code=f23set...)? My attempt is as below.
import { Route } from "react-router-dom";
import { useAuth0, withAuthenticationRequired } from "#auth0/auth0-react";
<Route
component={withAuthenticationRequired(component, {
onRedirecting: () => <span/>
})}
{...props} />

What is the best way to get shopOrigin from within Shopify react app?

Overview:
I am using Shopify's CLI to generate an embedded React app. I would like to be able to get the shopOrigin (see code below) from other pages within the application.
Problem:
I have attempted using store-js to store the shopOrigin. This works at first but it seems to clear when I navigate to any other pages within the application.
Note:
Since it is set properly in _app.js I feel that I should simply be able to get the shopOrigin further downstream (i.e. in my index page or any other page) without having to set it in storage.
So, I am completely open to obtaining the shopOrigin a different way without storing it via store-js or any other storage mechanism.
Nonetheless, here is my current code.
Code:
// _app.js
import store from "store-js";
// other imports
class MyApp extends App {
render() {
const { Component, pageProps, shopOrigin } = this.props;
store.set("shopUrl", shopOrigin);
return (
<AppProvider i18n={translations}>
<Provider
config={{
apiKey: API_KEY,
shopOrigin: shopOrigin,
forceRedirect: true,
}}
>
<ClientRouter />
<MyProvider Component={Component} {...pageProps} />
</Provider>
</AppProvider>
);
}
}
// index.js
import store from "store-js";
// other imports
function Index(props) {
console.log("SHOP URL IS HERE: " + store.get("shopUrl"));
}

Google Analytics setup for React

I have managed to set up Google Analytics for my React application with the ReactGA library so it will send the pageview to analytics when the user navigates around.
The problem
The problem I'm facing is that I'm not sending any analytics to google on the initial page load, since the history.listen method only fires when the location changes.
My setup
In the root of my project, I initialize the connection:
const history = require("history").createBrowserHistory;
import { Router } from "react-router-dom"
ReactGA.initialize(envConstants.ANALYTICS_TRACKING_ID);
const MyApp = () => (
<Router history={history}>
<MyRoutes />
</Router>
)
Since I only want to see which routes the users are on I have this in my router:
const MyRoutes = props => {
props.history.listen(location => {
// won't see this on initial load, but navigating to another route will give me this
console.log("LISTENING")
})
return (...)
}
So I wonder how I can come around this and send the first/ initial pageview when a user comes to my site. I believe I cannot achieve this with the history.listen method. So, I guess we have to add some other functionality that I'm not too sure of.
I appreciate all the help I can get with this. And if there's something unclear, please let me know.
Thanks for reading and have a nice day!
The issue is that history listen is not called on initial page load since it's only called when the location changes. Try something like the following
import { Router } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import ReactGA from 'react-ga';
const trackPageView = location => {
ReactGA.set({ page: location.pathname });
ReactGA.pageview(location.pathname);
};
const initGa = history => {
ReactGA.initialize('UA-XXXXXX-X', {
debug: true
});
trackPageView(history.location);
history.listen(trackPageView);
};
const history = createHistory();
initGa(history);
ReactDOM.render((
<Router history={history}>
<Layout />
</Router>
), document.getElementById('root'));

Authentication using ReactJS and Firebase

I'm new to web development and started learning ReactJS.
Till now there is no problem with building simple web apps.
Now I wanted to use authentication using Firebase.
I know how to authenticate a user using Firebase but I'm not able to get how to design frontend to restrict access to certain pages.
Previously, I used PHP and in that I used session variable and include to include the parts of HTML to show to the user.
But I don't know how to do it in ReactJS using Firebase.
Initial and failed approach:
I thought of updating this.state.loggedIn and using that to show the component. But this is not a correct way because I'm sending both the components to display and easily we can change the state of a component using React developer extension in Chrome.
This is my main index.js code:
import React from 'react';
import ReactDOM from 'react-dom';
import Login from './components/Login/Login';
import Home from './components/Home/Home';
import fire from './components/Fire';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: false,
};
this.authListener = this.authListener.bind(this);
}
componentDidMount() {
this.authListener();
}
authListener() {
fire.auth().onAuthStateChanged(user => {
if (user) {
// User is signed in.
console.log(user);
this.setState({
loggedIn: true
})
} else {
// No user is signed in.
this.setState({
loggedIn: false
});
}
});
}
render() {
return (
<div>
{this.state.loggedIn ? <Home/> : <Login/>}
</div>
);
}
}
ReactDOM.render(
<App/>, document.getElementById('app'));
And In Login.js, I'm calling the Firebase authentication function.
Just the snippet:
fire
.auth()
.signInWithEmailAndPassword(this.state.email, this.state.password)
.catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
// ...
});
The authentication is working fine and I can see it in the console log.
So, how should I do it in ReactJS + Firebase to authenticate?(Is it posible without using any other packages like react-routes etc?)
Or should I be using Cloud functions for this?
UPDATE:
Everything you link in your html files should always come from your public directory. So, keep your bundled .js files in your public directory.
I have wrote some time saving tips for beginners. You can find more about that beginners tips here.
It is actually simple.
If you want to send only required html file to the browser, you have to hit the server every time you need a whole different html.
You said that you used PHP. In PHP, we use sessions to know whether a person is logged-in or not.
So, we try to implement it here.
First lets look at how sessions in PHP work. Taken from an SO Answer
In the general situation :
the session id is sent to the user when his session is created.
it is stored in a cookie (called, by default, PHPSESSID)
that cookie is sent by the browser to the server with each request
the server (PHP) uses that cookie, containing the session_id, to know which file corresponds to that user.
The data in the sessions files is the content of $_SESSION,
serialized (ie, represented as a string -- with a function such as
serialize) ; and is un-serialized when the file is loaded by
PHP, to populate the $_SESSION array.
Sometimes, the session id is not stored in a cookie, but sent in
URLs, too -- but that's quite rare, nowadays.
For more informations, you can take a look at the Session
Handling section of the manual, that gives some useful
informations.
For instance, there is a page about Passing the Session ID, which
explains how the session id is passed from page to page, using a
cookie, or in URLs -- and which configuration options affect this.
So, session is just a cookie. Then I guess we can use a cookie to find out about the user login status.
In Firebase, there is a problem with cookies. You can only set cookie name as __session. Other names are ignored by the Firebase and as a result you can't read cookies on the server.
You need to use Firebase Functions to do some backend work and serve html based on the user login status.
So, setup the Firebase Functions in your project directory. In the root,
$ firebase init functions
Now you have to say to Firebase that any requests coming to your domain must invoke a function.
To do that you have to write rewrites in your firebase.json file. Our function name will be main.
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "/dev",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites":[{
"source": "**",
"function": "main"
}]
}
}
Now create an index.js anywhere(Prefer root of the project directory as this is the main file).
index.js
import express from 'express';
import * as functions from 'firebase-functions';
import fs from 'fs';
var cookieParser = require('cookie-parser')
const app = express();
app.use(cookieParser());
app.get('**', (req, res) => {
// Check the custom set cookie for user
// If the user is not logged-in, redirect to Login. Otherwise, redirect to Home page
if (req.cookies.__session == 'loggedin') {
var index = fs.readFileSync(__dirname + '/src/Home/index.html', 'utf8');
}
else {
var index = fs.readFileSync(__dirname + '/src/Login/index.html', 'utf8');
}
res.send(index);
});
export let main = functions.https.onRequest(app);
Some explanation about above parts:
express: It is used to process the requests and send the response.
firebase-functions: It is provided by the Google to use Firebase Functions.
fs : We read appropriate html according to the user login status from the file system and serve it.
cookie-parser: We use it to access our cookies easily.
Note: You need to convert it to ES5. So use babel command to convert it(I assume you saved index.js in your root.)
$ babel index.js -d functions
We are using cookie named __session to know about the user login status.
If the __session is set to loggedin, we send the user Home html. Otherwise, we send Login html.
Now, the real question is: "Where do we need to set the cookie __session?"
We set the cookie in the user browser.
We make use of onAuthStateChanged().
Call this method in your Login page component like:
componentDidMount() {
this.authListener();
}
authListener() {
fire.auth().onAuthStateChanged(user => {
if (user) {
// User is signed in.
if (Cookies.get('__session') === undefined) {
Cookies.set('__session', 'loggedin', { expires: 3652 });
window.location.reload();
}
} else {
// No user is signed in.
Cookies.remove('__session');
}
});
}
Note: Cookies object is imported from js-cookie. So, install it.
What we are doing here is:
First the onAuthStateChanged() is called initially when the page is loaded.
If the user is already logged-in, we enter into if block.
Then we check for the __session cookie. This is important because sometimes user can clear all the cookies. When we try to read the cookie in the server, we get undefined and we send the login page to the user. Process is repeated from 1.
Then we reload the page. When we reload the page, the user hits the server again. Then we can check for the __session cookie and send the Home html to the user.
Previously, I used PHP and in that I used session variable and include to include the parts of HTML to show to the user.
Firebase has a variety of ways it persists its auth state as described here: https://firebase.google.com/docs/auth/web/auth-state-persistence
By default it is local. On the Web, this is localStorage. This may be a drastic change for you since normally the auth state is in some sort of session or cookie that's done server side and client side.
So, how should I do it in ReactJS + Firebase to authenticate?(Is it posible without using any other packages like react-routes etc?)
But with Firebase, everything is done client side. Once a user has successfully authenticated, you will have access to their auth token as described here: https://firebase.google.com/docs/auth/users#auth_tokens
With this token, you would need to verify on the server side as described here: https://firebase.google.com/docs/auth/admin/verify-id-tokens in order to protect/secure any API access.
But for protecting certain pages in your React application, you will need to verify the auth state that is in localStorage in some sort of middleware. Preferably, you would have a listener for the auth state in one of your React lifecycle methods: https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged
At any point in your application, the auth state becomes invalid, block them from viewing the page.
Well, I show you how I used firebase Auth on a React App. I just want you to know that it's not THE RIGHT WAY, you'll find a lot of ways of doing it.
So let's begin, first I'm using the Google Auth, I've a file called firebase.service.js where I handle the auth. The file looks like:
import firebase from 'firebase';
import { firebaseConfig } from './private';
export const firebaseApp = firebase.initializeApp(firebaseConfig);
export const auth = firebase.auth();
export const db = firebase.database();
export function loginGoogle(/* options */) {
const provider = new firebase.auth.GoogleAuthProvider();
return firebase.auth().signInWithPopup(provider);
}
The firebaseConfig object looks like this:
const firebaseConfig = {
apiKey: API_KEY,
authDomain: AUTH_DOMAIN,
databaseURL: DB_URL,
projectId: PROJECT_ID,
storageBucket: STORE_BUCKET,
messagingSenderId: MSG_SENDER,
};
You can find it at Firebase Console.
At the index file of the app, where it's mounted, I wrapped the ReactDOM.render method with an auth check, to be sure if an user is or isn't logged. The index looks like:
import React from 'react';
import ReactDOM from 'react-dom';
import firebase from 'firebase';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { auth } from './services';
auth.setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(() => {
const stop = auth.onAuthStateChanged(() => {
ReactDOM.render(<App />, document.getElementById('root'));
stop();
});
});
registerServiceWorker();
Now you're sure that if an user go away and come back to the app he won't need to login again if his session stills valid.
The next step is to create the PrivateRoute Component. Here is what I've done:
import * as React from 'react';
import { Redirect, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import { auth } from '../services';
function PrivateRoute({ component: Component, ...rest }) {
const toRender = propsRender => (
auth.currentUser ?
<Component {...propsRender} /> :
<Redirect
to={{
pathname: '/',
state: { from: propsRender.location },
}}
/>
);
return <Route {...rest} render={toRender} />;
}
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
path: PropTypes.string.isRequired,
};
export default PrivateRoute;
Here you can see that the function checks if exists any logged user otherwise it sends the user back to the root path. Take note that I'm importing auth from the ./services not from the firebase, it's because this auth was initialized at firebase.service.js, then I use it through all application.
Now you can use this PrivateRoute as a common Route, the difference is that one will check if the user is logged. Take this as example:
import * as React from 'react';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
import { Layout } from 'antd';
import moment from 'moment';
import 'moment/locale/pt-br';
import 'antd/dist/antd.css';
import { PrivateRoute, MainLogged, Login, Navbar, Page404 } from './components';
const { Header } = Layout;
moment.locale('pt-br');
function Main() {
return <Redirect to="/login" />;
}
function App() {
return (
<BrowserRouter>
<Layout>
<Header style={{ paddingLeft: '0', backgroundColor: '#096dd9' }}>
<Navbar />
</Header>
<Switch>
<Route exact path="/" component={Main} />
<Route exact path="/login" component={Login} />
<PrivateRoute path="/app" component={MainLogged} />
<Route component={Page404} />
</Switch>
</Layout>
</BrowserRouter>
);
}
export default App;
As you can see PrivateRouse is beeing used side by side the Route component from react-router.
I thought that it's all, if you want to ask anything else just comment, and if you want to take a look on the project from where I take this codes, you can find it here.

Categories