I used Firebase for User Auth in my Next.js Application. I want to protect a user on the client-side. I used firebase firebaseui react-firebaseUI to implement Google Auth. How to secure a Route in the Client-side itself
const uiConfig = {
signInFlow: "popup",
signInSuccessUrl: "/dashboard",
signInOptions: [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
}
I want to protect the dashboard Route.
Thanks in advance :)
Router:
At first you'll need to maintain a router which would contain all your public and protected routes.
import React from 'react';
import {
Route,
Switch,
BrowserRouter,
} from 'react-router-dom';
import ProtectedRoute from './Protected';
import Foo from './Foo';
import Bar from './Bar';
const Routes = () => (
<BrowserRouter>
<Switch>
// public route
<Route exact path="/foo" component={Foo} />
// protected route
<ProtectedRoute exact path="/bar" component={Bar} />
// if user hits a URL apart from the above ones, render a 404 component
<Route path="*" component={NotFound} />
</Switch>
</BrowserRouter>
);
export default Routes;
Protected Route:
This is the protect route which would handle if a user should be allowed to access a protect page on your app based on it's auth status.
import React from 'react';
import { useAuthStatus } from '../hooks';
import Spinner from '../Spinner';
import UnAuthorised from '../UnAuthorised';
const ProtectedRoute = ({ component: Component }) => {
// a custom hook to keep track of user's auth status
const { loggedIn, checkingStatus } = useAuthStatus();
return (
<>
{
// display a spinner while auth status being checked
checkingStatus
? <Spinner />
: loggedIn
// if user is logged in, grant the access to the route
// note: in this example component is Bar
? <Component />
// else render an unauthorised component
// stating the reason why it cannot access the route
: <UnAuthorised />
}
</>
);
};
export default ProtectedRoute;
Firebase Auth Status:
Well, this is the custom hook to keep track of user logging in and logging out.
import { useEffect, useState } from 'react';
import { firebase } from '../config';
export const useAuthListener = (): void => {
// assume user to be logged out
const [loggedIn, setLoggedIn] = useState(false);
// keep track to display a spinner while auth status is being checked
const [checkingStatus, setCheckingStatus] = useState(true);
useEffect(() => {
// auth listener to keep track of user signing in and out
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setLoggedIn(true);
}
setCheckingStatus(false);
});
}, []);
return { loggedIn, checkingStatus };
};
Related
I'm making Authentication system using jwt with httpOnly cookies in node.js and React.js.
Question is- How should i handle if someone explicitly delete cookie(contain accesstoken) by going to inspect> application >cookies tab in browser?
In my case it should navigate to Login page but it just stay in the home page unless refreshed.
Home.js(send request to verify token on component mount but if cookie deleted stayed on the same page insted going to login page)
import axios from 'axios'
import React, { useEffect } from 'react'
import './Home.css'
import { useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux'
import Login from '../../Pages/Login';
function Home() {
const navigate = useNavigate()
useEffect(() => {
axios.get('http://localhost:9000/auth/redirecthome', { withCredentials: true })
.then((res) => {
console.log(res, 'response')
})
.catch((err) => {
console.log(err)
navigate('/login')
})
}, [])
return (
<div className='Home'>Home</div>
)
}
export default Home
Here is what I'm trying to do. I get a JWT from google, store it in sessionStorage for the time being, and send it through my API for server-side verification.
This is Protected Route which I'm using to wrap around my protected elements. Like so
<Route element={<PrivateRouteClient />}>
<Route path="/CDashboard" element={<Client_dashboard /> }>
</Route>
import axios from 'axios';
import React, {useEffect, useState} from 'react';
import {Navigate} from 'react-router-dom';
import {Outlet} from 'react-router-dom';
function PrivateRouteClient() {
const [isAuth, setIsAuth] = useState(false);
axios.post('http://localhost:8080/something/something', {credential: sessionStorage.getItem("token")})
.then(function (res){
console.log(Boolean(res.data["UserAuth"]));
setIsAuth(Boolean(res.data["UserAuth"]));
console.log("hi");
return isAuth;
})
console.log(isAuth)
return isAuth ? <Outlet /> : <Navigate to="/login/Client" />;
}
export default PrivateRouteClient
And I cant make the route async cause it will return a promise, and that's not a valid React Component and it throws and error.
Can't quite figure out where I'm doing wrong!
Thanks for reading and helping!
A couple issues, (1) you are issuing the POST request as an unintentional side-effect, and (2) the code is asynchronous whereas the body of a React component is synchronous.
Use a mounting useEffect hook to issue the POST request and wait for the request to resolve. Use an intermediate isAuth state value to "hold" until the POST request resolves either way if the user is authenticated or not.
function PrivateRouteClient() {
const [isAuth, setIsAuth] = useState(); // initially undefined
useEffect(() => {
axios.post(
'http://localhost:8080/something/something',
{ credential: sessionStorage.getItem("token") },
)
.then(function (res){
console.log(Boolean(res.data["UserAuth"]));
setIsAuth(Boolean(res.data["UserAuth"]));
return isAuth;
});
}, []);
console.log(isAuth);
if (isAuth === undefined) return null; // or loading indicator, etc...
return isAuth ? <Outlet /> : <Navigate to="/login/Client" />;
}
So Drew's answer helped me A LOT, thanks! useEffect is the way to go!
When we run this snippet, the "initial" value of log is "undefined" [this means the type of isAuth is also undefined]. When we run the axios.post, isAuth is set as a string, and not Boolean, so we cant really convert is using the Boolean() method.
function PrivateRouteClient() {
const [isAuth, setIsAuth] = useState();
console.log("initial")
console.log(isAuth)
useEffect(() => {
axios.post('http://localhost:8080/api/something',{credential: sessionStorage.getItem("token")}
).then(function (res){
console.log("setting IsAuth")
console.log(res.data["UserAuth"])
// console.log(Boolean(res.data["UserAuth"]));
setIsAuth(res.data["UserAuth"]);
return isAuth;
});
});
console.log(isAuth);
if (isAuth === undefined) return null; // or loading indicator, etc...
return isAuth==="true" ? <Outlet /> : <Navigate to="/login/Client" />;
}
Attaching the logs here for reference.
I am developing a fullstack app using React & Node.
The following is the home screen.
After a user logs in, the name of the user is displayed in the navbar and the response from the server (including the JWT) is saved in the local storage as shown in the following pictures:
Now, when I refresh the page the user is logged out. This should not happen because I am sending the token on every request header using axios global defaults as shown in the code snippet below:
frontend/src/App.js
import React from "react";
import { Container } from "react-bootstrap";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Products from "./components/Products";
import { Route, Switch } from "react-router-dom";
import SingleProductView from "./components/SingleProductView";
import Cart from "./components/Cart";
import LoginForm from "./components/LoginForm";
import RegisterForm from "./components/RegisterForm";
import Profile from "./components/Profile";
import Shipping from "./components/Shipping";
import PaymentMethod from "./components/PaymentMethod";
import PlaceOrder from "./components/PlaceOrder";
import axios from "axios";
const {token} = localStorage.getItem("loggedInUser");
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
const App = () => {
return (
<>
<Header />
<main className="py-3">
<Container>
<Switch>
<Route path="/placeorder" component={PlaceOrder} />
<Route path="/payment" component={PaymentMethod} />
<Route path="/shipping" component={Shipping} />
<Route path="/profile" component={Profile} />
<Route path="/register" component={RegisterForm} />
<Route path="/login" component={LoginForm} />
<Route path="/product/:id" component={SingleProductView} />
<Route path="/cart/:id?" component={Cart} />
<Route path="/" component={Products} exact />
</Switch>
</Container>
</main>
<Footer />
</>
);
};
export default App;
What am I doing wrong? I want the user to stay logged in even after page refresh.
Axios will lose its custom (provided by you) default headers settings after page is refreshed.
So, you need to set it again after page refresh (i.e. at App Start). You can do it in a component which always gets mounted before other components do:
In most cases, App component would be the perfect place for that:
// App.jsx
useEffect(() => {
const { token } = localStorage.getItem("loggedInUser");
if (token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
}
}, [])
Assuming, you are setting the Token for the first time in localStorage after the Authentication is successful. E.g. in Login component.
Another option could be to write a Request Interceptor for Axios and check if this header exists or not; if not, then set it back from localstorage and let the request proceed.
Using Request Interceptor:
axios.interceptors.request.use(
function (config) {
if (!config.headers.Authorization) {
const { token } = localStorage.getItem('loggedInUser')
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}` // this will be used in next requests
config.headers.Authorization = `Bearer ${token}` // this is for current request
}
}
return config
},
function (error) {
return Promise.reject(error)
}
)
Edit:
You are passing token to each AsyncThunkAction. So, all you need to do is initialize the redux state properly. So, in the loginSlice:
function getToken() {
const { token } = localStorage.getItem("loggedInUser");
if (token) {
return { token };
}
return null;
}
const initialState = {
status: "idle",
user: getToken(),
error: null,
};
I have some API in nodejs and a React App for client side. I try to create the auth system for my API/backoffice with a jwt token, I use jsonwebtoken for create and verify token on server side but I have some doubt for client side...now on login I save the token on localstorage, then with React-Router "onUpdate" I check if local storage has a token, if not I redirect to login else nothing append, then on my app I append an auth header for each ajax request.
This is my router
export const isLoggedIn = (nextState, replace) => {
console.log(localStorage.getItem('id_token'));
}
<Router history={browserHistory} onUpdate={isLoggedIn} >
<Route path="/" component={App}>
<IndexRoute component={Login.Login} />
<Route path="admin/" component={Dashboard} />
<Route path="admin/tutti" component={Users} />
</Route>
</Router>
Here I login
$.get('/login',credential, function (result) {
localStorage.setItem('id_token', result.token)
});
Generic request:
$.ajax({
url:"/api/users",
type:'GET',
contentType: "application/json",
success:function (result) {},
headers: {"x-access-token": localStorage.getItem('id_token')}
});
is this a correct way to manage the React auth flow?
my doubt is, on isLoggedIn I need to verify the token in some way?
thank you at all!
Do you know Higher Order Components?
Here is an article about HOC: https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.hb4ck2u52
React authentication flow can be written as a HOC.
For example:
import React, { Component, PropTypes } from 'react';
export default function (ComposedComponent) {
class Auth extends Component {
componentWillMount() {
const isLoggedIn = .... // your way to check if current user is logged in
if (!isLoggedIn) {
browserHistory.push('/'); // if not logged in, redirect to your login page
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
return Auth;
}
But I suggest you to use FLUX flow, such as Redux, and store your state in Redux store.
Here is my Redux implementation:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';
export default function (ComposedComponent) {
class Auth extends Component {
componentWillMount() {
if (!this.props.isLoggedIn) {
browserHistory.push('/login');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.isLoggedIn) {
browserHistory.push('/login');
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
Auth.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
};
function mapStateToProps(state) {
return { isLoggedIn: state.userReducer.isLoggedIn };
}
return connect(mapStateToProps)(Auth);
}
Usage:
import auth from '/path/to/HOC/Auth';
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Login.Login} />
<Route path="admin/" component={auth(Dashboard)} /> // wrap components supported to be protected
<Route path="admin/tutti" component={auth(Users)} />
</Route>
</Router>
There are two problems that Im facing currently, Im trying to do a basic GET request and it works just fine, but the problem lies when I refresh the page
For your information I'm doing a react-redux server rendering.
The url that the react-redux trying to consume is '/courses'
The Problems
1) '/courses' is the url for the server and for fetching in react-redux, and it works perfectly fine, in terms of navigation. For example from '/' (home page) to '/courses' page
it shows all the json data perfectly but when I refresh the page, it will show all the json data
Example
Home page
Courses Page
it shows perfectly but when I refresh the page, it shows all the json data not from the react-redux.
I know you guys will say that just change the URL from '/courses' -> '/api/courses' I did that and I get my second problem
2) Which is
ReferenceError: fetch is not defined, whenever I refresh the page
Here's the code
Routes
import React from 'react';
import { IndexRoute, Route } from 'react-router';
import App from './components/App';
import Home from './components/Home';
import Course from './components/Course';
export default function getRoutes(store) {
return (
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path='/courses' component={Course} />
</Route>
);
}
Action
export function getCourses() {
return (dispatch) => {
return fetch('/courses', {
method: 'get',
headers: { 'Content-Type': 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}
Main.js
import 'whatwg-fetch';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import configureStore from './store/configureStore';
import getRoutes from './routes';
const store = configureStore(window.INITIAL_STATE);
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory} routes={ getRoutes(store)} />
</Provider>,
document.getElementById('app')
)
And after that I just dispatch the action to the component which is Course.js which will render the component.
I'm really new to react-redux, and hope you guys could help me