Secure pages in sign in/up AWS Cognito React - javascript

I'm doing the sign in/up pages for an app, and I'm using AWS Cognito and React for the first time. I need to secure my pages until someone signs in/up. I couldn't figure out how to send anything from the userAuth() to the export default, or how to make this work.
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom';
import App from '../App';
import { Auth } from 'aws-amplify';
//last thing 333
async function userAuth() {
let something = Boolean;
Auth.currentSession()
.then(function(fulfilled) {
console.log('worked' + fulfilled);
something === true;
return something;
})
.catch(function(error) {
console.log('didnt work' + error);
window.location.href = '/';
return error;
});
}
export default ({ component: C, ...rest }) => (
alert('this is the user auth ' + userAuth()),
(
<Route
{...rest}
render={
props =>
userAuth() === 'something' ? (
<Redirect to="/" />
) : (
<C {...props} />
)
}
/>
)
);

Auth.currentAuthenticatedUser() is an asynchronous API, so you cannot return anything from this API that can be used in a Route (there is the possibility of returning a Promise, but that is not actually necessary.)
You can change the component to a class like:
class PrivateRoute extends React.Component {
constructor(props) {
super(props);
this.state = {
authStatus: false,
loading: false,
}
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then((user)=> {
this.setState({ loading: false, authStatus: true });
})
.catch(() => {
this.setState({ loading: false });
this.props.history.push('/login');
});
}
render() {
return <Route {...rest} render={(props) => (
this.state.authStatus
? <Component {...props} />
: <div>Loading ... </div>
)} />
}
}

Just fixed the problem, and thought it may help someone having the same issue !
import React from 'react';
import {
withRouter,
Switch,
Route,
Redirect,
BrowserRouter as Router
} from 'react-router-dom';
import { Auth } from 'aws-amplify';
class AppliedRoute extends React.Component {
state = {
loaded: false,
isAuthenticated: false
};
componentDidMount() {
this.authenticate();
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log('user: ', user))
.catch(() => {
if (this.state.isAuthenticated)
this.setState({ isAuthenticated: false });
});
});
}
componentWillUnmount() {
this.unlisten();
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true });
})
.catch(() => this.props.history.push('/'));
}
render() {
const { component: Component, ...rest } = this.props;
const { loaded, isAuthenticated } = this.state;
if (!loaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/'
}}
/>
);
}}
/>
);
}
}
AppliedRoute = withRouter(AppliedRoute);
export default AppliedRoute;

Related

How to render react routes after auth response?

When someone arrives to my webpage I first check if the user is authenticated or not. I need to wait for the response from my GET /auth/loggedin request before rendering the routes so that I know if I should redirect to /login or /. I have added conditional rendering to the App.js component, but it always redirects the user to /login even when the user is authenticated.
App.js:
import { useEffect, useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import AuthContext from "./contexts/authContext";
import { apiLoggedIn } from "./api/auth";
import ProtectedRoute from "./components/ProtectedRoute";
import Document from "./pages/Document";
import { Login } from "./pages/Login";
import { Signup } from "./pages/Signup";
import Layout from "./components/Layout";
import PublicRoute from "./components/PublicRoute";
export const App = () => {
const [auth, setAuth] = useState({ isAuth: undefined, user: undefined });
useEffect(() => {
apiLoggedIn()
.then((res) => {
setAuth({ isAuth: true, user: res.data.userData });
})
.catch((err) => {
if (err.response) {
setAuth({ isAuth: false, user: {} });
} else if (err.request) {
setAuth({ isAuth: false, user: {} });
console.log(err.request);
} else {
setAuth({ isAuth: false, user: {} });
console.log(err.message);
}
});
}, []);
return (
<>
{auth.isAuth !== undefined ? (
<AuthContext.Provider value={{ auth, setAuth }}>
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<ProtectedRoute redirectTo="/login">
<Layout />
</ProtectedRoute>
}
>
<Route index element={<Document />} />
</Route>
<Route
path="/signup"
element={
<PublicRoute>
<Signup />
</PublicRoute>
}
/>
<Route
path="/login"
element={
<PublicRoute>
<Login />
</PublicRoute>
}
/>
</Routes>
</BrowserRouter>
</AuthContext.Provider>
) : (
""
)}
</>
);
};
./api/auth.js
import axios from "axios";
export const apiLoggedIn = () => {
return axios.get("/auth/loggedin");
};
./components/ProtectedRoute.js
import { useContext } from "react";
import AuthContext from "../contexts/authContext";
import { Navigate } from "react-router-dom";
const ProtectedRoute = ({ children, redirectTo }) => {
const { isAuth } = useContext(AuthContext);
return isAuth ? children : <Navigate to={redirectTo} />;
};
export default ProtectedRoute;
./components/PublicRoute.js
import { useContext } from "react";
import AuthContext from "../contexts/authContext";
import { Navigate } from "react-router-dom";
const PublicRoute = ({ children }) => {
const { isAuth } = useContext(AuthContext);
return isAuth ? <Navigate to={"/"} /> : children;
};
export default PublicRoute;
./contexts/authContext.js
import { createContext } from "react";
const AuthContext = createContext({
auth: { isAuth: false, user: {} },
setAuth: () => {},
});
export default AuthContext;
The AuthContext value is an object with auth and setAuth properties
<AuthContext.Provider value={{ auth, setAuth }}>
...
</AuthContext.Provider>
But in the route wrapper you are referencing an isAuth property, which will always be undefined, i.e. falsey.
const ProtectedRoute = ({ children, redirectTo }) => {
const { isAuth } = useContext(AuthContext);
return isAuth ? children : <Navigate to={redirectTo} />;
};
This is why the redirect always occurs. To resolve, ensure you reference the same context values throughout the code.
Either specify an isAuth context value:
<AuthContext.Provider value={{ isAuth: auth.isAuth, setAuth }}>
...
</AuthContext.Provider>
Or fix the wrappers:
const ProtectedRoute = ({ children, redirectTo }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? children : <Navigate to={redirectTo} />;
};
...
const PublicRoute = ({ children }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? <Navigate to={"/"} /> : children;
};
Your context has the following shape :
{
"auth": {
"isAuth": true,
"user": {...},
},
"setAuth": function ...
}
you must update ProtectedRoute to
const ProtectedRoute = ({ children, redirectTo }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? children : <Navigate to={redirectTo} />;
};
you must update PublicRoute to
const PublicRoute = ({ children }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? <Navigate to={"/"} /> : children;
};
the key name is not passed correctly in auth provider it should be passed as following here is sample sandbox demo : https://codesandbox.io/s/routes-4c5oh?file=/src/App.js
<AuthContext.Provider value={{ isAuth:auth, setAuth }}>
or change the key to auth in each place. in Public and Protected routes like following
const { auth:{isAuth} } = useContext(AuthContext);

Redux resets after dispatching an action

so I've been working on this small cinema app and I'm facing a weird bug where every time I try to log in to access the home page it doesn't redirect and then the redux store gets reset, but when I commented useEffect in the HomePage.js component It worked and also when I tried to conosole.log(action.payload) in another reducer file for the movies I got the payload from the AuthReducer so I don't know what's the problem, is it with my whole redux setup or because I'm using the render method in my ProtecdedRoute.js
AuthSaga.js
function* loginUserSaga({ payload }) {
yield delay(1500);
yield put(LoginUserSuccessAction(payload));
}
export default function* AuthSaga() {
yield takeLatest(actionTypes.LOGIN_USER, loginUserSaga);
}
protectedRoute.js
import React from 'react';
import { useSelector } from 'react-redux';
import { Route, Redirect, useLocation } from 'react-router';
const ProtectedRoute = ({ component: Component, layout: Layout, ...rest }) => {
const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
if (!isLoggedIn) {
return (
<Redirect
to={{
pathname: '/login',
}}
/>
);
}
const renderComponent = () => {
return (
<Layout>
<Component />
</Layout>
);
};
return <Route {...rest} render={renderComponent} />;
};
export default ProtectedRoute;
Routes.js
const Routes = () => {
const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
console.log(isLoggedIn);
return (
<Switch>
<ProtectedRoute path="/" exact component={Home} layout={Layout} />
<AuthRoute path="/login" exact component={Login} layout={AuthLayout} />
</Switch>
);
};
export default Routes;
AuthReducer
const authReducer = (state = initialState, action) => {
const { type, payload } = action;
console.log(payload)
switch (type) {
case actionTypes.LOGIN_USER: {
return {
...state,
loading: true,
};
}
case actionTypes.LOGIN_USER_SUCCESS: {
return {
...state,
userName: payload.userName,
token: `${payload.password}${payload.userName}`,
isLoggedIn: true,
loading: false,
};
}
case actionTypes.LOGIN_USER_FAIL: {
return {
...state,
loading: false,
};
}
case actionTypes.LOGOUT_USER: {
return {
...state,
userName: null,
token: null,
isLoggedIn: false,
loading: false,
};
}
default:
return initialState;
}
};
HomePage.jsx
const Home = () => {
const dispatch = useDispatch();
const upcomingMoviesSelector = useSelector(state => state.upcomingMovies.items)
useEffect(() => {
dispatch(fetchUpcomingMoviesAction());
console.log('')
}, []);
return (
<div>
<MovieSlider items={upcomingMoviesSelector} />
</div>
);
};
export default Home;

Redirect not working on React, using react router dom

Here, is my full code details:
route.js
<AdminRoute path="/dashboard" component={Dashboard} />
And, my admin route:
AdminRoute
import React from 'react'
import { Route } from 'react-router-dom'
import AdminLayout from '../components/layouts/AdminLayout'
const AdminRoute = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => (
<AdminLayout>
<Component {...props} />
</AdminLayout>
)} />
)
}
export default AdminRoute
and this is the AdminLayout,
and here it is hitting that console.log correctly, but not redirecting.
AdminLayout/
import React from 'react';
import { Redirect } from "react-router-dom";
import { isAuthenticated } from '../../utils/auth';
const AdminLayout = ({ children, ...rest }) => {
const isLoggedIn = isAuthenticated();
console.log("isLoggedIn >>",isLoggedIn);
if(!isLoggedIn){
//ITS HITTING THIS CONSOLE LOG
//BUT ITS NOT REDIRECTING
console.log('hit');
<Redirect
to={{
pathname: "/login",
state: { from: children.props.location },
}}
/>
}
return (
<div>{children}</div>
);
}
and this is the helper function to determine either the user is logged in or not.
Auth.js
export function isAuthenticated(){
if(localStorage.getItem("_token")){
return true;
}
return;
}
const auth = {
isAuthenticated,
};
export default auth;
The Redirect component needs to be returned in order to be rendered, and, ultimately, trigger a redirect, i.e.
const AdminLayout = ({ children, ...rest }) => {
const isLoggedIn = isAuthenticated();
if (!isLoggedIn){
return <Redirect
to={{
pathname: "/login",
state: { from: children.props.location },
}}
/>
}
return (
<div>{children}</div>
);
}

PrivateRoute functional component using aws-amplify as authentication

I'm trying to create a functional component equivalent of the following PrivateRoute class component (source code here):
import React, { useState, useEffect } from "react";
import { Route, Redirect, withRouter, useHistory } from "react-router-dom";
import { Auth } from "aws-amplify";
class PrivateRoute extends React.Component {
state = {
loaded: false,
isAuthenticated: false
};
componentDidMount() {
this.authenticate();
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log("user: ", user))
.catch(() => {
if (this.state.isAuthenticated)
this.setState({ isAuthenticated: false });
});
});
}
componentWillUnmount() {
this.unlisten();
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true });
})
.catch(() => this.props.history.push("/auth"));
}
render() {
const { component: Component, ...rest } = this.props;
const { loaded, isAuthenticated } = this.state;
if (!loaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/auth"
}}
/>
);
}}
/>
);
}
}
export default withRouter(PrivateRoute);
The above code works in my application when I use it like so:
<PrivateRoute
exact
path={urls.homepage}
component={Homepage}
/>
My attempt at a converting the above class component into a functional component is the following:
import React, { useState, useEffect } from "react";
import { Route, Redirect, useHistory } from "react-router-dom";
import { Auth } from "aws-amplify";
const PrivateRoute = ({ component: Component, ...rest }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
let history = useHistory();
useEffect(() => {
Auth.currentAuthenticatedUser()
.then(() => {
setIsLoaded(true);
setIsAuthenticated(true);
})
.catch(() => history.push("/auth"));
return () =>
history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log("user: ", user))
.catch(() => {
if (isAuthenticated) setIsAuthenticated(false);
});
});
}, [history, isAuthenticated]);
if (!isLoaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/auth"
}}
/>
);
}}
/>
);
};
export default PrivateRoute;
But when using my functional component in the same way, I keep getting the following error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function
It always redirects me to /auth, regardless of whether I am logged in or not. What am I doing wrong? Any help is much appreciated!
I think you are missing your unmount, the return in the useEffect should be your unlisten which is the unmount. Also, I removed useHistory and pulled history from the props and used withRouter
Try this
import React, { useState, useEffect } from "react";
import { Route, Redirect, withRouter } from "react-router-dom";
import { Auth } from "aws-amplify";
const PrivateRoute = ({ component: Component, history, ...rest }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
Auth.currentAuthenticatedUser()
.then(() => {
setIsLoaded(true);
setIsAuthenticated(true);
})
.catch(() => history.push("/auth"));
const unlisten = history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log("user: ", user))
.catch(() => {
if (isAuthenticated) setIsAuthenticated(false);
});
});
return unlisten();
}, [history, isAuthenticated]);
if (!isLoaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/auth"
}}
/>
);
}}
/>
);
};
export default withRouter(PrivateRoute);
Try this out:
useEffect(() => {
async function CheckAuth() {
await Auth.currentAuthenticatedUser()
.then((user) => {
setIsLoaded(true);
setIsAuthenticated(true);
})
.catch(() => history.push("/auth"));
}
CheckAuth();
}, []);

Unable to implement auth routes

I'm trying to learn react, and am setting up routes in my application which require you to log in. I'm trying to adapt the example given here
The code I've written should either redirect the user or display the protected route. But when I log in I'm still being redirected.
I believe the issue is in my PrivateRoute class below. I pass it a authenticated property which is set in the parent class, but it doesn't appear to update.
In app.js we declare the authenticator, where we perform async login with our backend.
I pass the checkLoggedIn function to the login component, where we set the parent's authenticated state property to true. I'm console.log()ing the state just to check it's occurring, which it is.
When I then click the Link to /protected route I'm still being redirected.
app.js
// imports ...
let authenticator = new Authenticator();
class ProtectedComponent extends Component {
render() {
return (
<h1>Protected!</h1>
);
}
}
class App extends Component {
constructor(props){
super(props);
this.state = {
authenticator: authenticator,
authenticated: authenticator.isLoggedIn(),
}
}
checkLoggedIn() {
this.setState({authenticated: true});
console.log(this.state);
}
render() {
let routes, links = null;
links = <div className="links">
<Link to="/login">Login</Link>
<Link to="/protected">Protected</Link>
</div>;
routes = <div className="routes">
<Route
path="/login"
render={() =>
<Login
authenticator={this.state.authenticator}
loginCallback={this.checkLoggedIn} />
}
/>
<PrivateRoute
path="/protected"
component={ProtectedComponent}
authenticated={this.state.authenticated}
/>
</div>;
return (
<Router className="App">
{links}
{routes}
</Router>
);
}
}
export default App;
PrivateRoute.js
// imports ....
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route {...rest} render={props =>
authenticated === true
? (<Component {...props} />)
: (<Redirect to={{
pathname: "/login",
state: { from: props.location }
}} />
)
}/>
);
export default PrivateRoute;
Login.js
// imports ...
class Login extends Component {
constructor(props) {
super(props);
this.authenticator = props.authenticator;
this.loginCallback = props.loginCallback;
this.state = {
identifier: "",
password: "",
}
}
updateState = (e, keyName = null) => {
this.setState({[keyName]: e.target.value})
}
attemptLogin = (e) => {
this.authenticator.loginPromise(this.state.identifier, this.state.password)
.then(resp => {
if(resp.data.success === true) {
this.authenticator.setToken(resp.data.api_token);
this.loginCallback();
} else {
this.authenticator.removeToken()
}
})
.catch(err => {
console.error(err);
});
}
render(){
<button onClick={this.attemptLogin}> Log In </button>
}
}
export default Login;
I'm setting the authenticated state to true in the callback method, but when I go to the protected route (and run it's render method) it appears to be evaluating to false.
If I'm misunderstanding the react props system, let me know. If you'd like to see any more of the code let me know and I'll amend the question.
You have to create a PrivateRoute HOC component first:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('bpm-user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
and wrap your routes that most be protected:
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/new/index" component={NewIndex} />
<PrivateRoute path="/jobs/index" component={JobsIndex} />
<PrivateRoute path="/unions/index" component={UnionsIndex} />
<PrivateRoute exact path="/" component={ListIndex} />
<PrivateRoute exact path="/charges" component={MunicipalCharges} />
</Switch>
and use Link
<Link to="/jobs/index">Jobs</Link>
my login reducer
import axios from 'axios';
import * as actionTypes from './AuthActionTypes';
export const login = (user) => {
return dispatch => {
// for example => dispatch({type:actionTypes.REQUEST_LOGIN_USER});
axios({
method: 'post',
url: '/api/auth/login',
data: { 'username': user.username, 'password': user.password },
headers: { 'Content-Type': 'application/json;charset=utf-8' }
})
.then(res => {
localStorage.setItem('bpm-user', JSON.stringify(res.data));
dispatch({
type: actionTypes.LOGIN_USER,
payload: res.data
})
})
.catch(error => {
// TODO... for example => dispatch({type:actionTypes.FAILD_LOGIN_USER, payload:error});
})
}
}
export const logout = () => {
localStorage.removeItem('bpm-user');
}
like the example codes that i copied from my own project

Categories

Resources