I have an application that is using redux-oidc as a helper for authenticating users. I went through the example application on how to properly setup authenticating users, but in the example they would need to check if a user is logged in on every page before sending them to the login page.
The application I am building has a router with many different routes. I was wondering how to check if a user was logged on no mater which route they enter without having to duplicate the code for each component?
Router:
export default function Routes(props) {
return (
// Replace Router with our own component and add authenication
// https://medium.com/better-programming/building-basic-react-authentication-e20a574d5e71
<Router history={history}>
<Route path="/" component={HomePage}/>
<Route path="/test1" component={TestPage1}/>
<Route path="/test2" component={TestPage2}/>
<Route path="/test3" component={TestPage3}/>
<Route path="/callback" component={CallbackPage} />
</Router>
);
}
This is an example of a Router with 4 routes: Home, Test1, Test2, and Test3.
Home Page:
function HomePage(props) {
const { user } = props;
return !user || user.expired ? <LoginPage /> : (
<div>
<h1>Awesome Main Page!</h1>
<h3>{user ? user.profile.name : "Mister Unknown"}</h3>
</div>
);
}
In the Home Page component, we do a check for if the user is already authenticated. If they are not, then it goes to Login page.
Test Page:
function TestPage1(props) {
const { user } = props;
return !user || user.expired ? <LoginPage /> : (<div><h1>Test Page 1</h1><h3>{user ? user.profile.name : "Mister Unknown"}</h3></div>);
}
On each of the Test Page components in the routers, we also have to check if the user is authenticated. That seems to go against the DRY principles, and for each new route we create we would have to check for authentication again.
Is this the normal/proper way of checking authentication or is there a better way that abides by DRY principles?
no this is not the right way.
route.js
<Router history={history}>
<Switch>
<PublicRoute path="/" component={Home} exact= {true} />
<PrivateRoute path="/Test1" component = {Testpageone} />
<PrivateRoute path="/Test2" component= {Testpagetwo} />
</Switch>
</Router>
PrivateRoute.js
import {Redirect} from "react-router-dom"
const { user } = props;
return (
<Route
{...rest}
component={props =>
user ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
}
/>
);
PublicRoute.js
const { user } = props;
return (
<Route
{...rest}
component={props =>
user ? <Redirect to="/test1" /> : <Component {...props} />
}
/>
);
};
hope this will help
I've always found the most reliable option is to deal with logins in most apps as part of API calls:
UI fragment calls API
If there is no valid token, or it has expired, then redirect to login
This deals with both initial login and session expiry
Key classes tend to focus on the following responsibilities:
API Client
Authentication and token handling
Your React views can then be written in a productive way without any OAuth plumbing. If interested take a look at my code sample write up.
I don't claim to be a big React JS expert, but happy to answer follow up questions on OAuth handling if it helps.
Related
I'm trying to create a protected/private route with react-router-dom v5, as in the link here, I use only the code I needed - Protected routes and authentication with React Router v5.
Now I need both components private from each other
The problem: after Getting Home component transferred fromSignUp components I can go back to SignUp and thats not what I want.
Signup corrently is the first page (as sign-in). I don't want the user to go back to sigh-up components if he already signed up and present at the Home component.
The working website project is here - CodeSandbox
Anyone has idea how can I make both component protected/private?
GuardedRoute.jsx
function GuardedRoute({ children, auth, ...rest }) {
return (
<Route
{...rest}
render={() => {
return auth === true ? children : <Redirect to="/signup" />;
}}
/>
);
}
export default GuardedRoute;
App.js
const App = () => {
const [userID, setUserID] = useState('');
const [userSignedUp, setIfSignUp] = useState(false);
return (
<div className="App-div">
<GuardedRoute exact path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Switch>
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
</Switch>
</div>
);
};
export default App;
Please try any of your solutions at my codesandbox before posting your answer so we will not try solutions in theory only and in vain :)
You could make the signup route only exist if the user is not logged in, and then use a catch-all that will redirect to /home
<div className="App-div">
<Switch>
{!userSignedUp && (
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
)}
<GuardedRoute path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Route path="/">
<Redirect to="/home" />
</Route>
</Switch>
</div>
Updated sample: https://codesandbox.io/s/jolly-https-t7dmj?file=/src/containers/App.js
or you could encapsulate the logic to another component like GuardedRoute, let say UnguardedRoute that will redirect somewhere if user is logged in.
I am trying to build a role based access control React app.
My vision was that when the App mounts, it checks if user token exists. If it does, it runs checkAuthToken() and sets the state accordingly.
Where I am struggling is: redirection doesn't work as I expect it to.
Here is my code:
in App.js
function App() {
const { isAuthenticated, user } = useSelector(state => {
return state.userState;
});
const dispatch = useDispatch();
useEffect(() => {
checkAuthToken();
}, []);
return (
<Router>
<Switch>
<Route exact path='/'>
{!isAuthenticated ? (
<Redirect to='/login'/>
) : (
<Redirect to={`/${user.role}`} />
)}
</Route>
<Route
path="/login"
render={() => {
return <Login />;
}}
/>
<Route
path="/admin"
render={() => {
return <AdminDashboard user={user} />;
}}
/>
<Route
path="/staff"
render={() => {
return <OrderMenu user={user} />;
}}
/>
<Route component={ErrorPage} />
</Switch>
</Router>
);
}
export default App;
My understanding is React rerenders when state or props change. When I get the updated state from Redux, the app component should rerender and thus go through the isAuthenticated check and redirect accordingly, but i'm being proven wrong.
Currently, if there's no token, it redirects to Login route. After user logs in, token is set in localStorage. So to test it out, I close and open a new tab, try to go to path / expecting it to redirect me to /[admin|staff] route since checkAuthToken would successfully set the state but redirect doesn't work and just lands on /login. However, I could access /[admin|staff] if I type in manually.
I have this app, frontend is React and backend is Laravel. I did the auth system using Laravel Passport. When the user is logged in, I save the token in local storage. If the user is not logged in, I want to automatically redirect to /login. Basically the user can't do anything if it's not logged in. I tried this:
<BrowserRouter>
<Route path="/login" exact component={FormularAuth} />
{
isAuthenticated ? (
<React.Fragment>
*ALL OTHER ROUTES ARE HERE, AND IF LOGGED IN, IT WORKS*
</React.Fragment>
) : (
window.location = '/login')
)
}
</BrowserRouter>
It send's me to the /login page, but it keeps refreshing, and I can't login or anything. What is the problem here?
react router v4 supports use of PrivateRoute.
// create a private route, if user is not logged in redirect to login page
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
isAuthenticated() === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
Use it like a normal route and check authentication in the private route.
<BrowserRouter>
<Route path="/login" exact component={FormularAuth} />
<PrivateRoute path='/private' component={Private} />
</BrowserRouter>
I implemented auto-login via SSO for my root path "/" to simplify user's life by redirecting to "/login" with PrivateRoute and it works fine - it redirects to /login if not logged in and authenticates via SSO:
const Routes = () => (
<div className="view-routes">
<ErrorBoundaryRoute path="/login" component={Login} />
<ErrorBoundaryRoute path="/loggedout" component={HomePanel} />
<Switch>
<ErrorBoundaryRoute path="/logout" component={Logout} />
<PrivateRoute path="/" component={HomePanel} hasAnyAuthorities={[AUTHORITIES.USER]} />
</Switch>
</div>
);
The problem is my Logout component which looks like:
export class Logout extends React.Component<ILogoutProps> {
componentDidMount() {
this.props.logout();
}
render() {
return (
<div className="p-5">
<h4>Logged out successfully!</h4>
<Redirect
to={{
pathname: '/loggedout'
}}
/>
</div>
);
}
}
As you can see in Routes, /loggedout is mapped to the same HomePanel, but this time via "regular" route which should avoid automatic authentication.
It does log out, but when redirected to /loggedout for some reason it immediately after goes to "/" path which causes autologin again.
My question is: why doesn't it simply draw my HomePanel, but instead works as if It went through PrivateRoute again? In case it is how react is supposed to work how should I handle logout in such situation?
Your Switch statement is not correctly placed as it only validates /logout and /. If the user vistits /login, /loggedout. in both cases the respective Route will be rendered along with the / route
Change your code to
const Routes = () => (
<div className="view-routes">
<Switch>
<ErrorBoundaryRoute path="/login" component={Login} />
<ErrorBoundaryRoute path="/loggedout" component={HomePanel} />
<ErrorBoundaryRoute path="/logout" component={Logout} />
<PrivateRoute path="/" component={HomePanel} hasAnyAuthorities={[AUTHORITIES.USER]} />
</Switch>
</div>
);
Ok. React works as it should work and it is possible to use the same component in private and regular route.
The issue was caused by the fact that logout method has return url:
export const logout = () => dispatch => {
window.location.href = SAML_LOGOUT + `?return=${encodeURIComponent(LOGOUT_URL)}`;
};
So, redirect happened twice: one time in logout method and other time in component. I changed them to the same value and it works fine now.
I would like to know what is the best practise in my case.
I am checking cookies in App constructor if there is stored a token. If it is there I am dispatching action which will set property isAuth to true.
My problem is that I will get redirected to login because isAuth is false from the beginning.
I came up with solution that I will set isAuth after checking session and I will not redner anything until then. Is it OK? And even if is, isn't there a better solution for handling this?
return (
<div>
<Router>
{isAuth ? (
<Switch>
auth routes...
</Switch>
)
: (
<Switch>
login and redirect to login
</Switch>
)}
</Router>
</div>
);
I usually create a PrivateRoute component that renders the Route or Redirect components after checking the login status, something like:
export const PrivateRoute = ({ component: Component, ...otherProps }) => (
<Route
{ ...otherProps }
render={ props => (
isLoggedIn()
? <Component {...props} />
: <Redirect to={ Paths.LOGIN } />
)}
/>
)
You should replace my isLoggedIn method and my Paths.LOGIN constants with your case.
Then you just use them as:
<PrivateRoute path="/admin/something" component={ MyAdminPage } />
<Route path="/non/logged/in/route" component={ MyNonLoggedInPage } />
<Route path="/login" component={ LogInPage } />