How to stay logged in even after page refresh? - node.js

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,
};

Related

Pass res.data from one post request to different React component

I have created a login form in React that uses an Axios post request like this:
axios.post('http://localhost:8080/users/validate', {
email: email,
password: password,
})
.then((res) => {
setError(res.data);
//If validation passed
if (res.data.includes('Login successful')) {
navigate('/');
};
});
I would like to pass the res.data from this post request to a different React component. Specifically being the Header component so I can display the text from res.data.
This is the structure of the components in my React app
<>
<Header />
<Routes>
<Route path="/" element={<Index />}/>
<Route path="/results/:id" element={<Results />} /> {/* Passes route with ID from input */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register /> } />
<Route path="/users" element={<Users />}/>
<Route path="/products" element={<Products />}/>
<Route path="*" element={<Error />}/>
</Routes>
<Footer />
</>
With Login being the component containing the res.data and Header being the component I would like to send res.data too.
Ill include the back-end for in case their is a method to send this to that component from the back-end but ideally I would like to do it through the front-end perhaps using an axios request.
app.post('/users/validate', (req, res) => {
const email = req.body.email;
const password = req.body.password;
if (email && password) {
//Check all emails against input
db.query(selectEmail, [email], (err, rows) => {
if (err) throw err;
if (rows.length > 0) {
//If password and email match then login
if (HashPassword(password, rows[0].salt) == rows[0].password) { //Checks password and compares to hash
res.send(rows[0].first_name);
}
else {
res.send('Incorrect password');
};
}
else {
res.send('Email or password are incorrect');
};
});
};
});
In a simplified version I want to send the first_name row from my back end to the Header component on my front-end. But I am struggling to understand how to do this because the post request containing first_name is found under is currently being used under the Login component.
How about passing with the state prop in useNavigate - hook as
axios
.post("http://localhost:8080/users/validate", {
email: email,
password: password,
})
.then((res) => {
setError(res.data);
//If validation passed
if (res.data.includes("Login successful")) {
navigate("/", {state: res.data}); // like so
}
});
and In Header component you can get the value using useLocation hook
You should use a state management library like Redux or React Context
With the state management library, you can access your state in all the components of your app.

Protected Routes in React Router v6, using useState, but always returns false

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.

Protected Route With Firebase

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 };
};

react authentication flow verification

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>

fetching URL using react-redux works but if i refresh the page, it won't work

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

Resources