I want to create a protected route that only authenticated users can access. It works BUT when im refreshing the page the context is undefined and redirects the user to the landing page. I dont really see why this is happening.
Console log after refreshing page
App.js
import React, { useState, useEffect } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Axios from "axios";
import Home from "./components/pages/Home";
import LandingPage from "./components/pages/LandingPage";
import { ProtectedRoute } from "./components/auth/ProtectedRoute";
import UserContext from "./context/UserContext";
import "./style.css";
export default function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
const checkLoggedIn = async () => {
let token = localStorage.getItem("auth-token");
if (token === null) {
localStorage.setItem("auth-token", "");
token = "";
}
const tokenRes = await Axios.post(
"http://localhost:5000/users/tokenIsValid",
null,
{ headers: { "x-auth-token": token } }
);
if (tokenRes.data) {
const userRes = await Axios.get("http://localhost:5000/users/", {
headers: { "x-auth-token": token },
});
setUserData({
token,
user: userRes.data,
});
}
};
useEffect(() => {
console.log('useEffect called');
checkLoggedIn();
}, []);
return (
<>
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Switch>
<Route exact path="/" component={LandingPage} />
<ProtectedRoute exact path="/home" component={Home} />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
ProtectedRoute.js
import React, { useContext } from "react";
import UserContext from "../../context/UserContext";
import { Route, Redirect } from "react-router-dom";
export const ProtectedRoute = ({
component: Component,
...rest
}) => {
const { userData } = useContext(UserContext);
return (
<Route
{...rest}
render={props => {
console.log("USERDATA " + userData.user)
console.log("TOKEN " + localStorage.getItem("auth-token"))
if (userData.user) {
return <Component {...props} />;
} else {
return (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
EDIT
After a while i figured out the problem, which is explained in this question REACT - Check authentication before render APP
After a while i figured out the problem, which is explained in this question REACT - Check authentication before render APP
This is my solution, which works fine.
App.js
export default function App() {
const [userData, setUserData] = useState({
isLoggedIn: false,
isLoading: true,
token: undefined,
user: undefined
});
const checkLoggedIn = async () => {
const tokenRes = await validateToken();
if (tokenRes.data) {
const userRes = await getUser();
let token = localStorage.getItem("auth-token");
setUserData({
token,
user: userRes.data,
isLoggedIn: true,
isLoading: false
});
}
};
useEffect(() => {
console.log('useEffect called');
checkLoggedIn();
}, []);
console.log("APPPPPP")
return (
<>
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Switch>
<Route exact path="/" component={LandingPage} />
<ProtectedRoute exact path="/home" component={Home} />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
ProtectedRoute.js
import React, {useContext} from "react";
import { Route, Redirect } from "react-router-dom";
import UserContext from "../../context/UserContext";
const ProtectedRoute = ({ component: Comp, path, ...rest }) => {
const { userData } = useContext(UserContext);
console.log(userData.isLoggedIn);
return (
<Route path={path} {...rest}
render={props => {
return userData.isLoggedIn ? (<Comp {...props} />) : (userData.isLoading ? 'Loading...' : <Redirect to="/" />)
}}
/>
);
};
export default ProtectedRoute;
Related
I have a react app and I would like the user to be redirected to the '/dashboard' when logged in and not the home route '/'. I have accomplished this with the '/login' route, but cannot get it to do the same for the '/' home route.
Any help or insight would be appreciated
occasionally when playing around I get the infinite loop error from React
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Auth } from "aws-amplify";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
import { Provider } from "react-redux";
import ProtectedRoute from "./Utils/ProtectedRoute";
import PublicRoutes from "./Utils/PublicRoute";
import { AuthState, onAuthUIStateChange } from "#aws-amplify/ui-components";
import store from "./store";
import { userLogIn, userLogOut } from "./Actions/userActions";
import NavBar from "./Components/NavBar";
import Home from "./Screens/Home";
import AmplifySignUp from "./Components/AmplifyLogIn";
import Dashboard from "./Screens/Dashboard";
const Routes = (props) => {
const [authState, setAuthState] = useState();
const [user, setUser] = useState();
const [userName, setUserName] = useState();
const dispatch = useDispatch();
const userState = useSelector((s) => s.user);
useEffect(() => {
return onAuthUIStateChange((nextAuthState, authData) => {
setAuthState(nextAuthState);
setUser(authData);
});
});
useEffect(() => {
if (AuthState.SignedIn && user) {
dispatch(userLogIn(user.attributes.email));
}
}, [user]);
useEffect(() => {
if (authState !== AuthState.SignedIn) {
dispatch(userLogOut());
}
}, [user]);
// check auth
const isAuth = async () => {
try {
const status = await Auth.currentAuthenticatedUser();
return status.username;
} catch (err) {}
};
// Auth.currentAuthenticatedUser()
// .then((user) => {
// console.log(user.username)
// setUserName(prevState => {
// if(prevState !== user.username){
// return user.username
// }else {return prevState}
// }
// )
// })
// .catch((err) => console.log(err));
return (
<Router>
<NavBar />
<Switch>
<Route exact path="/">
{userName ? <Redirect from='/' to="/dashboard" /> : <Home />}
</Route>
<Route path="/login">
{authState === AuthState.SignedIn && user ? (
<Redirect to="/dashboard" />
) : (
<AmplifySignUp />
)}
</Route>
<ProtectedRoute path="/dashboard" user={user}>
<Dashboard />
</ProtectedRoute>
</Switch>
</Router>
);
};
export default Routes;
ProtectedRoutes.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
const ProtectedRoutes = ({user, children, ...rest }) => {
console.log(user && user)
return (
<Route
{...rest}
render={() => {
return user ? (
children
) : (
<Redirect
to='/'
/>
);
}}
/>
);
};
export default ProtectedRoutes;
Issue
On this line...
{userName ? <Redirect from='/' to="/dashboard" /> : <Home />}
You're not updating the username state with the currently authenticated user, hence the dashboard redirect never happens.
Possible Solution
Add a function to get the currently authenticated user
Add a useEffect hook to update the username state with the returned user
Code
const getAuthenticatedUser = async () => {
try {
const user = await Auth.currentAuthenticatedUser();
return {
user,
};
} catch (error) {
return { error };
}
};
useEffect(() => {
const { user, error } = await getAuthenticatedUser();
if (!error) {
setUsername(user.username);
}
}, []);
I'm using reach router for my routes. I was able to protect the dashboard and expose the login page and redirect if the user is not logged in but now if I enter a url it will do a quick redirecto to login and then to home instead of the page actually entered.
I noticed because of the useEffect to fetch the user the component renders twice: 1 without user (redirects to login) the other one with user and redirects to home.
Routes file
const AdminRoutes = () => {
return (
<Router>
<MainLayout path="/admin">
<HomePage path="/" />
<CarTransfer path="/cartransfers">
<CarTranserList path="/list" />
<CarTransferCreate path="/new" />
</CarTransfer>
<User path="/users">
<UserList path="/list" />
<UserCreate path="/new" />
</User>
</MainLayout>
<LoginPage path="/login" />
</Router>
);
};
Layout file
import { useState, useEffect } from "react";
import { Redirect, useNavigate } from "#reach/router";
import { Layout } from "antd";
import SiderMenu from "./SiderMenu";
import LayoutBanner from "./LayoutBanner";
import { useSelector, useDispatch } from "react-redux";
import {
userSelector,
fetchUserBytoken,
clearState,
} from "../../features/authSlice";
const { Content } = Layout;
const MainLayout = ({ children }) => {
const user = useSelector(userSelector);
const [collapsed, setCollapsed] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const { isFetching, isError } = useSelector(userSelector);
useEffect(() => {
dispatch(
fetchUserBytoken({
token: localStorage.getItem("token"),
id: localStorage.getItem("id"),
})
);
}, []);
useEffect(() => {
if (isError) {
dispatch(clearState());
navigate("/login");
}
}, [isError]);
const handleOnCollapse = () => {
setCollapsed((prevState) => !prevState);
};
if (isFetching) {
return <div>Loading</div>;
}
if (user.id === "") {
return <Redirect noThrow to="/login" />;
}
return (
<Layout>
<SiderMenu collapsed={collapsed} handleOnCollapse={handleOnCollapse} />
<Layout>
<LayoutBanner
collapsed={collapsed}
handleOnCollapse={handleOnCollapse}
/>
<Content>{children}</Content>
</Layout>
</Layout>
);
};
export default MainLayout;
The second question would be how to get to the same page you were before the login redirect after login.
Thanks
I have two Private Routes in my application but they keep pointing to the same dashboard component.
App.js
function App() {
return (
<Router>
<AuthProvider>
<Header />
<div>
<Switch>
<Route path="/" exact component={Home}/>
<RegistrationRoute path="/signup" component={SignUp} />
<RegistrationRoute path="/login" component={Login} />
<PrivateRoute path="/user/name" component={DetailForm} exact={true}/>
<PrivateRoute path="/dash" component={Dashboard}/>
</Switch>
</div>
<Footer />
</AuthProvider>
</Router>
);
}
PrivateRoute
import React from 'react'
import { Route, Redirect } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const PrivateRoute = ({ component: Component, ...rest }) => {
const { isAuth, currentUser, loading } = useAuth();
console.log(rest)
return (
<Route
{...rest}
render={props => {
return isAuth ? <Component {...props} /> : <Redirect to="/login" />
}}/>
)
}
RegistrationRoute
import React from 'react'
import { useAuth } from '../contexts/AuthContext';
import { Route, Redirect } from 'react-router-dom';
const RegistrationRoute = ({ component: Component, ...rest }) => {
const { isAuth } = useAuth();
return (
<Route
{...rest}
render={props => !isAuth ? <Component {...props} /> : <Redirect to="/dash" />}>
</Route>
)
}
My login function pushes to "/dash"
My SignUp function is trying to go to "/user/name"
but I keep getting redirected to /dash
Help me please and thank you <3
EDIT: AuthContext
import React, { useContext, useState, useEffect } from 'react'
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { login, signup, logout, checkAuth } from '../util/AuthRoutes';
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
}
export const AuthProvider = ({ children }) => {
const [isAuth, setIsAuth] = useState();
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const history = useHistory();
const signUp = async (signUpInfo) => {
const result = await signup(signUpInfo);
if(result.data.auth) {
setCurrentUser(result.data.user);
setIsAuth(true);
history.push("/user/name");
}
}
const logIn = async (loginInfo) => {
setLoading(true);
const result = await login(loginInfo);
console.log(result);
if(result.data.auth) {
setLoading(false);
setCurrentUser(result.data.user);
setIsAuth(true);
history.push("/dash");
}
}
const logOut = () => {
setIsAuth(false);
setLoading(true)
axios.get('http://localhost:4000/logout', { withCredentials: true })
.then(res => {
setLoading(false);
console.log(res);
setCurrentUser(null);
history.push("/");
})
.catch(err => {
console.log(err);
})
}
useEffect(() => {
setLoading(true);
axios.get('http://localhost:4000/checkauth', { withCredentials: true })
.then(res => {
if(res.data.auth){
setCurrentUser(res.data.session.user);
setLoading(false);
setIsAuth(true);
console.log(res);
}
}).catch(err => {
console.log(err);
setLoading(false);
history.push("/")
})
}, [isAuth]);
const value = {
currentUser,
setCurrentUser,
signUp,
logIn,
logOut,
error,
loading,
isAuth
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
export default AuthContext
I am creating a private route to be accessed only when the firebase listener verifies that the user is logged in, but I cannot access this route the way I am doing.
const PrivateRoute = ({ component: Component, ...rest }) => {
let autenticado = false;
firebase.auth().onAuthStateChanged((user) => {
if (user) {
autenticado = true;
} else {
autenticado = false;
}
});
return (
<Route
{...rest}
render={(props) =>
autenticado ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
/>
);
};
const Routes = () => (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Login} />
<PrivateRoute path="/home" component={Home} />
</Switch>
</BrowserRouter>
);
export default Routes;
Alvaro's solution is good, but Robert Terrell brought up a good point:
if you refresh your app, the private route kicks you back to the login page because it takes a few moments for the currentUser to re register
.
To fix this, id wait for the authcontext to load before displaying the site on line 24 in AuthContext.tsx:
import React, { useEffect, useState } from "react";
import {auth, firebase} from "../firebaseSetup";
type ContextProps = {
user: firebase.User | null;
};
export const AuthContext = React.createContext<Partial<ContextProps>>({});
export const AuthProvider = ({ children }: any) => {
const [user, setUser] = useState(null as firebase.User | null);
const [loading, setLoading] = useState(true);
useEffect(() => {
auth.onAuthStateChanged((user: any) => {
setUser(user);
setLoading(false);
});
}, []);
return (
<AuthContext.Provider
value={{user}}>
{!loading && children}
</AuthContext.Provider>
);
}
It's hard to tell where your code is going wrong without seeing other components. I do basically the same approach when I set up firebase auth and set up private routes. I bring my context into my PrivateRoute component like so:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "./Auth";
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/admin/login"} />
)
}
/>
);
};
My Auth.js file is where I create my context like so:
import React, { useEffect, useState } from "react";
import app from "./base.js";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider
value={{
currentUser
}}
>
{children}
</AuthContext.Provider>
);
};
I initiate my firebase instance in a file called base.js that looks like so :
import * as firebase from "firebase/app";
import "firebase/auth";
console.log(process.env.REACT_APP_FIREBASE_DATABASE)
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
});
export default app;
In your routes you would then add the AuthProvider
const Routes = () => (
<BrowserRouter>
<Switch>
<AuthProvider>
<Route path="/" exact component={Login} />
<PrivateRoute path="/home" component={Home} />
</AuthProvider>
</Switch>
</BrowserRouter>
);
export default Routes;
I have an application with multi user login functionality. Now, I created the redux store for the login function. If User logged in, based on their type, it will redirect to the particular dashboard. If student user logged in, he should be able to only access student dashboard, he should not able to access other dashboards. Same applies for others. But, now, if any user logged in, they can able to access other user dashboards. How can i prevent them to use only their dashboards.
Updated With Proper Code
/***AppRouter***/
import React, {Component} from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import StuDashboard from '../views/ed/dashboard/Dashboard.js';
import AdminDashboard from '../views/biz/dashboard/Dashboard.js';
import OrgDashboard from '../views/org/dashboard/Dashboard.js';
import SupAdDashboard from '../views/me/dashboard/Dashboard.js';
import UserLogin from '../views/loginUser/Login.js';
import history from '../history/history.js';
import { PrivateRoute } from './PrivateRoute.js';
import NotFoundPage from '../views/NotFoundPage.js';
import Authorization from './Authorization';
class AppRouter extends Component {
render () {
return(
<BrowserRouter history={history}>
<Switch>
<Route path="/" component={Landing} exact />
<Route path="/confirmation" component={EmailCon}/>
<PrivateRoute path="/student/dashboard" component={Authorization(StuDashboard),["Student"]}/>
<PrivateRoute path="/admin/dashboard" component={Authorization(AdminDashboard),["Admin"}/>
<PrivateRoute path="/org/dashboard" component={Authorization(OrgDashboard),["Org"]}/>
<PrivateRoute path="/SuperAdmin/dashboard" component={Authorization(SupAdDashboard),["SuperAdmin"]}/>
<Route path="/login" component={UserLogin}/>
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>
);
}
}
export default AppRouter;
/***PrivateRoute***/
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('token')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
/***Authorization***/
import React, { Component } from 'react';
import { connect } from "react-redux";
const Authorization = (WrappedComponent, allowedRoles) =>{
class WithAuthorization extends Component {
render() {
const userType = this.props.user
if (allowedRoles.includes(userType)) {
return <WrappedComponent {...this.props} />
} else {
return <h1>You are not allowed to view this page!</h1>
}
}
}
const mapStateToProps = state => ({ user: state.login.userName, userType: state.login.userType })
return connect(mapStateToProps)(WithAuthorization);
}
export default Authorization;
/***Action.js***/
import axios from "axios";
import { LOGIN_PENDING, LOGIN_COMPLETED, LOGIN_ERROR, LOGOUT } from "./types";
import ApiUrl from "../../constants/ApiUrl.js";
import qs from "qs";
import history from '../../history/history.js';
const startLogin = () => {
return {
type: LOGIN_PENDING
};
};
const loginComplete = data => ({
type: LOGIN_COMPLETED,
data
});
const loginError = err => ({
type: LOGIN_ERROR,
err
});
export const loginUser = (email, password) => {
return dispatch => {
dispatch(startLogin());
let headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
const data = qs.stringify({
grant_type: "password",
username: email,
password: password,
});
axios
.post(`${ApiUrl}/Token`, data, {
headers: headers
})
.then(function (resp) {
dispatch(loginComplete(resp.data));
localStorage.setItem("token", resp.data.access_token);
switch (resp.data.userType) {
case 'Admin': {
history.push('/admin/dashboard');
break;
}
case 'Student': {
history.push('/student/dashboard');
break;
}
case 'Organization': {
history.push('/org/dashboard');
break;
}
case 'SuperAdmin': {
history.push('/SuperAdmin/dashboard');
break;
}
default:
history.push('/');
}
window.location.reload();
return;
})
.catch(err => dispatch(loginError(err)));
};
};
export const logOut = () => {
localStorage.clear();
return {
type: LOGOUT,
};
}
I added an Authorization HOC to cater for access control. So you just pass the component, and the allowed role to the Authorization HOC.First i assume your user has a property userType. check out this URL.
/***Authorization***/
import React, { Component } from "react";
import { connect } from "react-redux";
const Authorization = (WrappedComponent, allowedRoles) => {
class WithAuthorization extends Component {
render() {
const userType = this.props.userType;
if (allowedRoles.includes(userType)) {
return <WrappedComponent {...this.props} />;
} else {
return <h1>You are not allowed to view this page!</h1>;
}
}
}
const mapStateToProps = state => ({ user: state.login.userName, userType: state.login.userType })
return connect(mapStateToProps)(WithAuthorization);
};
export default Authorization;
your router will then look this way
<BrowserRouter history={history}>
<Switch>
<Route path="/" component={Landing} exact />
<Route path="/confirmation" component={EmailCon}/>
<PrivateRoute path="/student/dashboard" component={Authorization(StuDashboard,["Student"])}/>
<PrivateRoute path="/admin/dashboard" component={Authorization(AdminDashboard,["Admin"])}/>
<PrivateRoute path="/org/dashboard" component={ Authorization(OrgDashboard,["Organization"])}/>
<PrivateRoute path="/SuperAdmin/dashboard" component={Authorization(SupAdDashboard,["SuperAdmin"])}/>
<Route path="/login" component={UserLogin}/>
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>