PrivateRoute component for authentification - javascript

i am developping a Reactjs-nodejs application. I would like to make a JWT authentification. when we log in, i give a unique token to the user. Then, thanks to this token, if it is valid, i allow the user to navigate through my router. my private route component is like :
PrivateRoute
My function getId is like that:
async function getId(){
let res = await axios('_/api/users/me',{config}).catch(err => { console.log(err)});
return res+1;
}
Finally the config component is the token stored in the localStorage :
const config = {
headers: { Authorization: ${window.localStorage.getItem("token")} }
};
GetId() returns the id of the user if logged in, else it is null.
The problem now is that my privateRoute always redirect to "/" path. I think it is because of the axios(promise) that gives me the userId too late. please tell me if you understand well and if you have a solution.
Thanks you

You can create private-route like this .
const PrivateRoute = ({ component: Component, ...props }) => {
return (
<Route
{...props}
render={innerProps =>
localStorage.getItem("Token") ? (
<Component {...innerProps} />
) : (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
)
}
/>
);
};
then you can use
<PrivateRoute path="/" component={FIlname} />

if you are using redux, you can make the following:
in reduer:
case LOGIN_ADMIN_SUCCESS:
localStorage.setItem("token", action.payload.token);
return {
...state,
user: action.payload,
isSignedIn: true,
loadingSignIn: false,
token: action.payload.token,
};
then in your private router you have to check these values, like
const PrivateRoute = ({ component: Component, admin, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (admin.loadingSignIn) {
return <h3>Loading...</h3>;
} else if (!admin.isSignedIn) {
return <Redirect to="/login" />;
} else {
return <Component {...props} />;
}
}}
/>
);

Related

load routes after getting roles from an api

I created a react app, I added role based mechanism, the idea is that after the athentication, directly I send a request to an api to get the roles of the user, because the token contains only the username and I can not add any information on the payload.
so while getting thr roles from the api, I added a loder component that will block the user from using the app until the roles are loaded, at this point everything is working well, but when I reloaded the page, the app redirect me to the default route everytime, because the routes are not created yet, I would like to know how to block the app until the routes also are loaded? :
App.tsx :
const App: React.FC = () => {
const useRoles = useRoleBased(); // hook to get roles
return (
<>
{useRoles?.loading(
<Loader open backgroundColor="#ffffff" circleColor="primary" />
)}
<Box className={`mainSideBar ${openSideBar && 'openSideBar'}`}>
<Router />
</Box>
</>
);
};
export default App;
Router.tsx :
const routes = [
{ path: '/logout', element: <ConfirmLogout /> },
{
path: '/dashboard-page',
element: <DashboardPage />,
allowedRoles: [Roles.Director, Roles.RequestFullAccess],
},
{
path: '/profil',
element: <RequestPage />,
allowedRoles: [Roles.Manager],
},
];
const Router: React.FC = () => {
return <RolesAuthRoute routes={routes}></RolesAuthRoute>;
};
export default Router;
RolesAuthRoute.tsx :
export function RolesAuthRoute({ routes }: { routes: IRoutes[] }) {
const userRoles = useSelector((state: any) => state?.roles?.roles);
const isAllowed = (
allowedRoles: Roles[] | undefined,
userRoles: string[]) =>
process.env.REACT_APP_ACTIVATE_ROLE_BASED_AUTH === 'false' ||
!allowedRoles ||
allowedRoles?.some((allowedRole) => userRoles?.includes(allowedRole)
);
return (
<Routes>
{routes.map((route) => {
if (isAllowed(route?.allowedRoles, userRoles))
return (
<Route
path={route?.path}
element={route?.element}
key={route?.path}
/>
);
else return null;
})}
<Route path="*" element={<Navigate to="/" replace />} /> //this route is created in all cases
</Routes>
);
}
You could return early (conditional rendering) to stop the router from rendering prematurely. You'll need to modify the hook to return the loading state as boolean instead of rendering the component as it seems to be currently implemented.
const App: React.FC = () => {
const useRoles = useRoleBased(); // hook to get roles
if(useRoles.isLoading){
return <Loader open backgroundColor="#ffffff" circleColor="primary" />
};
return (
<>
<Box className={`mainSideBar ${openSideBar && 'openSideBar'}`}>
<Router />
</Box>
</>
);
};
export default App;

React context api lose auth data when react router dom push page

I have an context where i save the user data, and i have another component when verify the context user is null, if the context user is null my component should redirect the user to the login page, if not should render the component. My routers is inside my Authprovider, but still losing the user data when reload the router. I found another posts with the same issue, and the instruction is to keep the routers inside the useauthprovider, but doesn't work with my app.
My code
function App() {
let header = window.location.pathname === '/login' || '/cadastro' ? <Header /> : null;
let footer = window.location.pathname === '/login' || '/cadastro' ? <Footer /> : null;
return (
<UseAuthProvider> // My use AuthProvider
<Router>
<div className='app-container' >
<Switch>
<Cart>
<Header />
<NavbarMenu />
<div className='app-body'>
<UseCampanhaProvider>
<PublicRoute exact path='/' component={Home} />
<PrivateRoute exact path='/cupom/:campaignId' component={CupomScreen} />
<PrivateRoute exact path='/carrinho' component={CartScreen} />
</UseCampanhaProvider>
<PublicRoute exact path='/login' restricted={true} component={Login} />
<PublicRoute path='/cadastro' restricted={true} component={Cadastro} />
</div>
<AuthModal />
{footer}
</Cart>
</Switch>
</div>
</Router >
</UseAuthProvider>
);
}
export default App;
My component where i verify the user context
const PrivateRoute = ({ component: Component, ...rest }) => {
const { user } = useAuth();
return (
<Route {...rest} render={props => (
!user ?
<Redirect to='/login' />
:
<Component {...props} />
)} />
);
};
export default PrivateRoute;
My context where i load the user
const UseAuthProvider = ({ children }) => {
const [user, setUser] = useState();
const [open, setOpen] = useState(false)
useEffect(() => {
verifyUser(); //here i call the function when verify the localstorage
}, [])
const verifyUser = async () => {
let tokenHeader = authHeader();
if (tokenHeader) {
await Api.post('/cliente/index', {}, {
headers: {
...tokenHeader
}
}).then((response) => {
setUser(response.data.cliente)
})
}
}
const handleModal = () => {
setOpen((state) => !state)
}
const Logout = async () => {
localStorage.clear('acessToken-bolao')
setUser(null)
}
return (
<useAuthContext.Provider value={{ Auth, verifyUser, user, Register, Logout, open, handleModal }}>
{children}
</useAuthContext.Provider>
)
}
I tried to debug my application and when i redirect my user to another router, before the component render my user return undefined, and after my component is rendered the context load the user data.
It sounds like your entire application is unmounting and remounting.
In this case the state will be lost as it is not simply a re-render.
By what mechanism are you navigating to the new page?
If I remember React-Router correctly you need to use
If you try navigating the url itself with window.location or href then you are reloading the entire page (not using the router in the SPA)
If routed correctly I would expect that only data inside the Switch would be re-loaded.

How to get a return value from async function to a variable in React Native?

I have a React Native app with an authentication process. To get the Authentication, I use Async Storage to store the token and retrieve the token.
I created a method to get the token from the Async storage, but the problem is, I can't use the token in other functions by assigning it to another variable. The function call of the method I created always returns a Promise instead of the token.
App.js
const getToken = async () => {
var value = "";
try {
await AsyncStorage.getItem('accessToken').then(val => {
value = val;
})
} catch (e) {
console.log("error getting token", e)
}
console.log("token", value) // here I can see the correct token
return value;
}
const AuthRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => {
if (Auth.isAuthenticated()) {
var idToken = getToken().then(t => { return t });
console.log("token", idToken) // here it is a Promise
if (idToken) {
return <Component {...props} />
}
return <Redirect to={{ pathname: `/signup` }} />
}
else {
// return <Component {...props} />
return <Redirect to={{ pathname: `/signup` }} />
}
}} />
)
How can I solve this?
Once you have a Promise, all the code depending on the promise will need to be chain using Promise.then(functionThatDoesMoreThings) or you will need to await Promise... and then do things.
Most people prefer async/await... so in your case it will look like:
const AuthRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={async props => {
if (Auth.isAuthenticated()) {
var idToken = await getToken();
console.log("token", idToken) // it should print the token
if (idToken) {
return <Component {...props} />
}
return <Redirect to={{ pathname: `/signup` }} />
}
else {
// return <Component {...props} />
return <Redirect to={{ pathname: `/signup` }} />
}
}} />

Provide formik form value to context provider

I have a <Login> component/form that uses <Formik> to keep track of my forms state. There's a specific value props.values.userType that I want to pass to a context provider so I can pass this prop down to my routing component.
My goal is to redirect users that aren't logged in as admin and if they are indeed an admin proceed to render the route as normal.
So I created an AuthContext.
const AuthContext = React.createContext();
In my <Login> component I have the <Formik> component below. Where should I use AuthContext.Provider and how should I pass values.props.userType to that provider? Should props.values.userType be initialized in state of the class component this <Formik> component lives in ?
Or should I create an object store in state that keeps track of the userType? Something like this
export const AuthContext = createContext({
user: null,
isAuthenticated: null
});
I have a codesandbox here.
class FormikLoginForm extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const {} = this.state;
return (
<Formik
initialValues={{
username: "",
password: "",
userType: "",
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
validationSchema={Yup.object().shape({
userType: Yup.string().required("User type is required"),
username: Yup.string().required(
"Required -- select a user type role"
),
password: Yup.string().required("Password is required"),
})}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset
} = props;
return (
<>
<Grid>
<Grid.Column>
<Header as="h2" color="teal" textAlign="center">
Log-in to your account
</Header>
<Form size="large" onSubmit={handleSubmit}>
<Segment stacked>
<RadioButtonGroup
id="userType"
label="User Type"
value={values.userType}
error={errors.userType}
touched={touched.userType}
>
Then in my index.js file, where I render all my routes, I have my AdminRoute that uses the logic I described above
const AdminRoute = props => {
const { userType, ...routeProps } = props;
if (userType !== "admin") return <Redirect to="/login" />;
return <Route {...routeProps} />;
};
const Routes = () => (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={FormikLoginForm} />
<Route exact path="/admin" component={AdminPage} />
/>
<Route path="/admin/change-password" component={ChangePassword} />
</Switch>
</Router>
);
As you are using React Router I would recommend following the example in their docs for authentication flow and create a PrivateRoute component and use that in place of the regular Route component.
Formik itself wouldn't be the solution to this, it's simply a way to make it easier to interact with forms and perform form validation. Letting the user pass their own type that's later used for authorization does not seem to be a good idea unless that's also somehow required for the authentication flow.
As for the userType it seems to me it's something you should get as a claim from a successful login via an API endpoint, or from whatever login backend you are using. And yes, you could store that in your AuthContext and use it like so (assuming you use React 16.8+ in your project setup):
function AdminPrivateRoute({ component: Component, ...rest }) {
const auth = React.useContext(AuthContext);
return (
<Route
{...rest}
render={props =>
auth.isAuthenticated && auth.userType === 'admin' ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
The AuthContext.Provider component should be used close to, if not at, the top of the component tree. In your code sample I'd say in the Routes component. You probably also want to implement a way to interact with the context as it will need to be dynamic. Based on the React documentation it could look something like this:
// may want to pass initial auth state as a prop
function AuthProvider({ children }) {
const [state, setState] = React.useState({
isAuthenticated: false,
userType: null,
// other data related to auth/user
});
// may or may not have use for React.useMemo here
const value = {
...state,
// login() does not validate user credentials
// it simply sets isAuthenticated and userType
login: (user) => setState({ isAuthenticated: true, userType: user.type }),
// logout() only clears isAuthenticated, will also need to clear auth cookies
logout: () => setState({ isAuthenticated: false, userType: null }),
};
return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
}

React Router 4 Async Rendering

I am following the guide on React Router 4 for Redirect(Auth) and I am having trouble rendering base on the promise the ajax returns. I'm not sure why my rendering inside the promise is not being returned. Could someone point me to the right direction?
import React from 'react';
import {
Route,
Redirect,
withRouter
} from 'react-router-dom';
import HeaderContainer from '../containers/HeaderContainer';
const PrivateRoute = ({ component: Component, ...props }) => {
const validated = (rest) => {
props.fetchUser()
.then(() => {
return (
<div>
<HeaderContainer />
<Component {...rest}/>
</div>
)
})
.catch(()=> {
return (
<Redirect to={{
pathname: '/signin',
state: { from: props.location }
}}/>
)
}
);
}
return (
<Route {...props} render={rest => {
return (
<div>
{ validated(rest) }
</div>
)
}}/>
)
}
export default withRouter(PrivateRoute);
My routes look like this
const Root = ({ store }) => {
return (
<Provider store={ store }>
<BrowserRouter onUpdate={() => window.scrollTo(0, 0)}>
<div className="root">
<Switch>
<Route exact path="/signin" component={SignInContainer}/>
<PrivateRouteContainer exact path="/" component={HomePageContainer} />
</Switch>
</div>
</BrowserRouter>
</Provider>
)
};
Thats because promise cannot return value, it only returns Promise. Instead it execute callbacks. Here is some explanation.
You could rearrange your code to somewhat like this:
class PrivateRoute extends React.Component {
constructor(props){
super(props);
this.state = {
isFetching: true,
isSuccess: null,
};
}
componentDidMount() {
this.props.fetchUser()
.then(() => {
this.setState({ isFetching: false, isSuccess: true });
})
.catch(()=> {
this.setState({ isFetching: false, isSuccess: false });
});
}
render() {
const { isFetching, isSuccess } = this.state;
return (
<Route {...this.props} render={rest => {
const success = (
<div>
<HeaderContainer />
<Component {...rest}/>
</div>
);
const error = (
<Redirect to={{
pathname: '/signin',
state: { from: this.props.location }
}}/>
);
if(isFetching) {
return null;
}
return isSuccess ? success : error;
}}/>
)
}
}
Notice that Promise doesn't return anything it just executing a callback which triggers rerender with new data in state.

Categories

Resources