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.
Related
I'm trying to declare a boolean from my firebase.js configuration file that depends on an if-else statement that checks whether a user is logged in or not.
I want to store the log in status of a user in a boolean variable and export that variable across different components in my React app.
So if user is logged in I want my variable isLoggedIn to be true and export that. But I want the isLoggedIn boolean to be updated depending on the log-in status before it gets exported.
Here is my firebase.js file
import { initializeApp } from 'firebase/app';
import { getAuth } from "firebase/auth";
const firebaseConfig = {
//private keys
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
//Problem
let isLoggedIn = false;
function checkLoggedInStatus() {
if (auth.currentUser !== null) { //i.e. user is signed in
isLoggedIn = true;
}
return isLoggedIn;
}
checkLoggedInStatus();
export isLoggedIn;
There are (at least) two problems here.
What immediately follows export may be, among other things:
A declaration followed by a variable name (eg export let foo = 5;) - this initializes the variable and exports it from the module in a single line
function or class - similar to the above
default, if you want to indicate what you're exporting is the default export
Brackets, if you're exporting identifiers that have already been declared - eg export { foo }
But doing export isLoggedIn; is invalid syntax - it falls into none of those categories (nor any of the other permitted constructs).
The other issue is that the .currentUser property may not exist yet if the auth isn't done initializing. Make sure you wait for it to be done first. Use the observer version instead.
Since that is asynchronous, you have a few options for getting that information to the rest of your script. The first one, that I'd recommend, is to have this auth module call the rest of your script (in other words, make this module your entry point, or close to it). For example:
import { initializeApp } from 'firebase/app';
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { renderApp } from './renderApp';
const firebaseConfig = {
//private keys
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
onAuthStateChanged(auth, (user) => {
if (user) {
renderApp(user);
} else {
// No user
renderApp();
}
});
where renderApp is the starting point for the rest of your code that depends on the user. You can't easily export isLoggedIn directly from the firebase module because it's retrieved asynchronously - calling renderApp with it and then passing the value around is the best way to do it.
You can also export a Promise from this firebase module script, one that resolves to whether the user is logged in or not, but that'll require all consumers of the module to have to call .then on the value before being able to use it, which could be quite messy. Better to use your firebase module script as an entry point and only call the rest of your script once the auth object has finished loading.
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"));
}
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: { } };
}
I am using React Router in my current project:
const store = Redux.createStore(bomlerApp);
const App = React.createClass({
render() {
return (
React.createElement('div', null,
this.props.children
)
)
}
})
var rootElement =
React.createElement(ReactRedux.Provider, {store: store},
React.createElement(ReactRouter.Router, {history: ReactRouter.browserHistory},
React.createElement(ReactRouter.Route, { path: '/', component: App },
React.createElement(ReactRouter.IndexRoute, { component: Home })
)
)
)
ReactDOM.render(rootElement, document.getElementById('react-app'));
This does not work. The app does not render at all and I don't get any error messages.
However, if I use ReactRouter.hashHistory instead, the app works.
What am I not understanding here?
Server Configuration: the browser history setup can generate real
looking urLs without reloading the page. But what happens if the user
refreshes or bookmarks on a deep nested urL? these urLs are
dynamically generated at the browser; they do not correspond to real
paths on the server, and since any urL will always hit the server on
the first request, it will likely return a page not Found error.
To implement the browser history setup, you need to import the
createBrowserHistory method from the History library. You can then
invoke it, passing the generated browser history configuration as the
history prop of the Router component
***> to work with browser history setup, you need to make rewrite
configurations on your server, so when the user hits /some-path on the
browser, the server will serve index page from where react router will
render the right view.***
I have been thinking and I am confused with the routing between Client and Server. Suppose I use ReactJS for server-side rendering before sending the request back to web browser, and use react-router as a client-side routing to switch between pages without refreshing as SPA.
What comes to mind is:
How are the routes interpreted? For example, a request from Home page (/home) to Posts page (/posts)
Where does the routing go, on server-side or client?
How does it know how it is processed?
Note, this answer covers React Router version 0.13.x - the upcoming version 1.0 looks like it will have significantly different implementation details
Server
This is a minimal server.js with react-router:
var express = require('express')
var React = require('react')
var Router = require('react-router')
var routes = require('./routes')
var app = express()
// ...express config...
app.use(function(req, res, next) {
var router = Router.create({location: req.url, routes: routes})
router.run(function(Handler, state) {
var html = React.renderToString(<Handler/>)
return res.render('react_page', {html: html})
})
})
Where the routes module exports a list of Routes:
var React = require('react')
var {DefaultRoute, NotFoundRoute, Route} = require('react-router')
module.exports = [
<Route path="/" handler={require('./components/App')}>
{/* ... */}
</Route>
]
Every time a request is made to the server, you create a single-use Router instance configured with the incoming URL as its static location, which is resolved against the tree of routes to set up the appropriate matched routes, calling back with the top-level route handler to be rendered and a record of which child routes matched at each level. This is what's consulted when you use the <RouteHandler> component within a route handling component to render a child route which was matched.
If the user has JavaScript turned off, or it's being slow to load, any links they click on will hit the server again, which is resolved again as above.
Client
This is a minimal client.js with react-router (re-using the same routes module):
var React = require('react')
var Router = require('react-router')
var routes = require('./routes')
Router.run(routes, Router.HistoryLocation, function(Handler, state) {
React.render(<Handler/>, document.body)
})
When you call Router.run(), it creates a Router instance for you behind the scenes, which is re-used every time you navigate around the app, as the URL can be dynamic on the client, as opposed to on the server where a single request has a fixed URL.
In this case, we're using the HistoryLocation, which uses the History API to make sure the right thing happens when you hit the back/forward button. There's also a HashLocation which changes the URL hash to make history entries and listens to the window.onhashchange event to trigger navigation.
When you use react-router's <Link> component, you give it a to prop which is the name of a route, plus any params and query data the route needs. The <a> rendered by this component has an onClick handler which ultimately calls router.transitionTo() on the router instance with the props you gave the link, which looks like this:
/**
* Transitions to the URL specified in the arguments by pushing
* a new URL onto the history stack.
*/
transitionTo: function (to, params, query) {
var path = this.makePath(to, params, query);
if (pendingTransition) {
// Replace so pending location does not stay in history.
location.replace(path);
} else {
location.push(path);
}
},
For a regular link this ultimately calls location.push() on whichever Location type you're using, which handles the details of setting up history so navigating with the back and forward buttons will work, then calls back to router.handleLocationChange() to let the router know it can proceed with transitioning to the new URL path.
The router then calls its own router.dispatch() method with the new URL, which handles the details of determining which of the configured routes match the URL, then calls any transition hooks present for the matched routes. You can implement these transition hooks on any of your route handlers to take some action when a route is about to be navigated away from or navigated to, with the ability to abort the transition if things aren't to your liking.
If the transition wasn't aborted, the final step is to call the callback you gave to Router.run() with the top-level handler component and a state object with all the details of the URL and the matched routes. The top-level handler component is actually the Router instance itself, which handles rendering the top-most route handler which was matched.
The above process is re-run every time you navigate to a new URL on the client.
Example projects
React Router Mega Demo
Isomorphic Lab
With 1.0, React-Router depends on the history module as a peerDependency. This module deals with routing in the browser. By default React-Router uses the HTML5 History API (pushState, replaceState), but you can configure it to use hash-based routing (see below)
The route handling is now done behind the scenes, and ReactRouter sends new props down to the Route handlers when the route changes. The Router has a new onUpdate prop callback whenever a route changes, useful for pageview tracking, or updating the <title>, for example.
Client (HTML5 routing)
import {Router} from 'react-router'
import routes from './routes'
var el = document.getElementById('root')
function track(){
// ...
}
// routes can be children
render(<Router onUpdate={track}>{routes}</Router>, el)
Client (hash-based routing)
import {Router} from 'react-router'
import {createHashHistory} from 'history'
import routes from './routes'
var el = document.getElementById('root')
var history = createHashHistory()
// or routes can be a prop
render(<Router routes={routes} history={history}></Router>, el)
Server
On the server, we can use ReactRouter.match, this is taken from the server rendering guide
import { renderToString } from 'react-dom/server'
import { match, RoutingContext } from 'react-router'
import routes from './routes'
app.get('*', function(req, res) {
// Note that req.url here should be the full URL path from
// the original request, including the query string.
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
res.status(200).send(renderToString(<RoutingContext {...renderProps} />))
} else {
res.status(404).send('Not found')
}
})
})