How to save Information of user in React after user login - javascript

I write a small app with React and back-end is PHP. I have 2 type user in database (admin and student). After user login, I save information in session Storage like that ( user: { username:'abcxyz123', role: 'admin' } ). The component render based user.role. This working good. But If I open DevTools and change user.role, my app will wrong render (because user.role is very simple text). How can I avoid this ??? My code is look like that.
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
user: (window.sessionStorage.user)
? JSON.parse(window.sessionStorage.user)
: false,
};
}
checkUserToLogin = (e) => {
if( loginSuccess ){
// return data of user in variable finalData
// finalData = { username:'abcxyz123', role: 'admin' }
window.sessionStorage.setItem('user', JSON.stringify(finalData));
this.setState({ user: JSON.parse(window.sessionStorage.user) });
}
}
render() {
const {
user
} = this.state;
return (
<div>
<form onSubmit={ this.checkUserToLogin }>
<input type="text" />
<input type="password" />
<button type="submit"> Login </button>
</form>
{/*Component will render based props user.role */}
<Component user={user} />
</div>
)
}
}
I can't change my database. The data of role always 'admin' or 'student'.

If a check is done from your backend on every authenticated action, it shouldn't be a problem.
What I think you do wrong
I think you send authenticated information to the front and let it handle if they should be shown or not. Which is really bad. Every informations sent in request can possibly be read, even if it's not render in DOM. The php backend should filter information based on database role.
The solution
Only keep a token or something that authenticates your front user in its XHR request. JWT is a really great way to do it cause it can not be mutate from the front.
Handle the render or not of admin action but continue to check it in every backend request.
If the information are changed by a malicious user, it's going to be kick from backend and you don't care if the front is broken for him.
To go further
It can be interesting too to keep this token and information in a global context. For example you can use React.Context or Redux and synchronise it with your local storage.
So you don't need to go through props driling with your user data.

Related

How do I store my Spotify access_token local so I can access it in different Javascript files

Hi Im relatively new to coding with Javascript, and Im trying to work out how I can access Spotify API's access_token within different javascript files. Im running two local server, one React and the other in node. The React server has a button that allows the user to login with Spotify's Oauth in the node server and then the access token and refresh token are redirect successfully back to the react server. The OAuth works quite well as I got it from the Spotify API guide they provide. The way I set up these files is that in my main project I have a auther-server older which includes the Spotify OAuth files and a client folder which is where i create my React app in. Here is where i got the OAuth files( https://github.com/spotify/web-api-auth-examples ). The only things changed in these files was in the app.js found in authorization_code folder where I entered my client and secret id with the correct redirect url, and I also did:
res.redirect('http://localhost:3000/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
where I made the redirect link go to http://localhost:3000/# with the tokens which is my React app is. From there I go to my React directory and in the App.js file I follow a guide i found on youtube which helps me get what the user is now playing ( https://www.youtube.com/watch?v=prayNyuN3w0&t=1496s). Here is the code:
import React, { Component, useEffect } from "react";
import "./App.css";
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor() {
super();
const params = this.getHashParams();
this.state ={
loggedIn: params.access_token ? true : false,
nowPlaying: {
name: 'Not Checked',
image: '',
}
}
if (params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
}
getHashParams() {
var hashParams = {};
var e,
r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ((e = r.exec(q))) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
this.setState({
nowPlaying:{
name: response.item.name,
image: response.item.album.images[0].url
}
})
})
}
render() {
return (
<div className="App">
<a href="http://localhost:8888">
<button>Login With Spotify</button>
</a>
<div>Now Playing: {this.state.nowPlaying.name}</div>
<div>
<img src={ this.state.nowPlaying.image } style={{width: 100}}/>
</div>
<button onClick={() => this.getNowPlaying()}>Check Now Playing</button>
<br></br>
</div>
);
}
}
export default App;
This all works nicely, but Im trying to now access the user's data( playlists) so I can later make new recommendations using Naive Bayes and KNN classifiers, but Ill tackled that after I get over this bit first. Ive looked up ways of storing the tokens and found localStorage.set(token....) to store it in the browser but I havent had success with it as I am confused on where to set this in my code. I think its also worth to note im using the spotify web api js libary, https://github.com/jmperez/spotify-web-api-js . Im guessing that i would make a similar function in the class App like the getNowPlaying() to get the playlists, but I keep getting lost. This is my first question on here, and I probably didnt do a good job of explaining my issue here, but i hope i can find some help on here. Thanks and let me know if I need to provide any more information. :)
You are probably on a good lead to do what you want with localStorage.set.
It is globally accessible. You can to the localStorage.set from wherever you retrieve the token to store it. Then you will be able to do the localStorage.get from wherever you are in your app.
Using the localStorage is a good idea because it will help you keep this token between page refresh. You might want to remove it from localStorage at some point though for security purpose if someone might have access to the machine you use.
Another way if you do not want to use the localStorage is setting it on a separate JavaScript file that you would import wherever you might want to use it.
You can even look for store principles if you want to make that data centralization a step further.

React JS - How do I prevent users from tampering their cookies and gaining access to protected routes/endpoints?

I have a React app wherein the user object is stored as cookies in the App component. This user object has a property AccountType which is an integer, and 1 means that the user is a student and 2 means the user is a teacher.
I am using react-router v5 to protect certain routes from being accessed, unless the logged in user is of AccountType 2 (teacher) with the following RouteGuard component:
const RouteGuard = ({ component: Component, ...rest }) => {
const user = JSON.parse(Cookies.get("loggedInUser"));
return (
<Route
{...rest}
render={(props) => {
return user.AccountType === 2 ? (
<Component />
) : (
<Redirect
to={{
pathname: "/403",
state: {
error: "You are not allowed to access this resource.",
from: props.location.pathname,
redirected: true,
},
}}
/>
);
}}
/>
);
};
And an example of how this RouteGuard component is used:
<Switch>
<RouteGuard path="/records" exact component={Records} />
</Switch>
It works well, for normal cases, but I found out that I can login as a student and go to the developer console and in the cookies section, I can modify the cookies and manually set AccountType to 2, thereby bypassing the route protection.
What would be the proper way of preventing unauthorized users from tampering cookies and gaining access to protected endpoints, front-end wise?
There is no way to disallow this. The best method would be to store a version of any username in the local storage, then compare that data to a server-side database to figure out if that user has the required account type.
This is a similar question:
How to secure localStorage in HTML5?
If you get this value from the backend and store it in local storage it will solve your problem. But The best approach will be authenticate using the JWT token and pass the necessary info into it.

Secure authentication structure in a React Webapp

I am currently learning React and worked through some courses but still haven't completely understood how to create a proper structure for a secure web app.
For the sign in, sign up flow I use the firebase SDK. Once logged in, a user gets redirected to a private route. Right now I only have 2 user roles. Guests and signed in Users. This enables me to create private routes by using an inbuild firebase function. This is the first problem as it is not scalable once I add different roles as it would force me to send a request to the backend to check what role the user is and thus which pages he can acces.
if (firebase.auth().currentUser === null) {
console.log("not logged in")
return (<Redirect
to={{
pathname: "/signin",
state: {
from: props.location
}
}}
/>);
}
So I thought that the easiest option would be to use Context, which did work. Once a user loggs in, the server sends a user object which the app refers to for the rest of the session. I followed a bunch of tutorials and they all had the same problem that when using chrome developer tools with the react features, you could just edit the state of the user and bypass the private routes etc.
Second Try:
<UserContext.Consumer>{(context)=>{
const {isLoggedIn} = context
return(
<Route
{...rest}
render={props => {
if (isLoggedIn) {
console.log("not logged in")
return (<Redirect
to={{
pathname: "/signin",
state: {
from: props.location
}
}}
/>);
I'd be grateful if somebody could point me in a direction as it seems like I am missing something important.
EDIT 1: Or is it simply that once you build the app, you can no longer access these states and it's considered safe?
when using chrome developer tools with the react features, you could just edit the state of the user and bypass the private routes
Your routes will never be truly private. They are part of the JavaScript bundle that gets downloaded and rendered by the browser, so they should never contain anything secret. Anyone could read this code if they really wanted to.
Consider this:
if (loggedIn) {
return <div>Secret data: ABC</div>;
}
The string "ABC" is contained in your app build, and is not really a secret anymore. The average user wouldn't know how to obtain it, but a developer probably would, for example by toggling some state in the developer console.
However, the data that comes from Firestore (or any another backend service) should be properly protected. Permission checks are done server-side before this data is sent to the browser. So, unless the user has the required permissions, the data will never be exposed to the wrong person, even if someone tampers with your client-side code in the developer console.
if (loggedIn) {
fetchDataFromBackend();
}
It doesn't matter if someone changes loggedIn to true so that fetchDataFromBackend() is called; the server will make sure the data isn't returned unless the user has the proper permission (e.g. is logged in). In the case of Firebase (Firestore), this protection is achieved with Security Rules.
And, by the way, the recommended way to get the current user with Firebase is to add a listener to the Auth object:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
You could put this in a top-level component and share the user object with child components through a context. That way you don't have to call firebase.auth() all over the place. Here's a good starting point if you need some inspiration: https://usehooks.com/useAuth/
I think what you are doing on the frontend site is good, but you would also need logic in the backend to protect your routes. This means that an user may be able to circumvent your route protection via dev tools on the frontend, but your backend would only send error messages to him, as it recognizes that he has no allowance.
You could do this with Higher Order Functions like this one:
const authenticationWrapper = createResolver(
async ( models, session, SALT ) => {
try {
if (!session) {
throw new Error("No valid credentials!");
}
const { id } = verify(session.token, salt);
const valid = databasecall //
if (!valid) {
throw new Error("No valid user!");
}
return true
} catch (error) {
session.destroy((err) => {
if (err) {
console.error(err);
}
});
}
}
);
All private backend functions would be wrapped into and authentication of the user would be checked every time.
The principle to check in Front- and Backend is called dual authentication. You can read more about it here.

Handling Pre-Fetched Dynamic Client Routes in Gatsby / #ReachRouter

I have this scenario.
A user types in /company/<company-id> in the address bar.
Since the app is totally separate from the backend, it needs to prefetch the companies.
Normal user flow is /login -> /company/. I handle this case pretty well and just navigate to /company/<whatever id is first in the prefetch> with no problems.
But what if you load WITH the id? I have solution but I think I have a feeling that I'm misunderstanding something in routing.
You may assume that my prefetching works and the code snippet below will only trigger if companyState.success is true. Like i said, it is working.
I handled this manually, by
// pretty sure i can handle this with regex better to capture other cases
// but that's beside the point for the scope of this question
const urlId = +location.pathname.replace("/company/", "")
const checkCompany = !!companyState.data.find(d => d.id === urlId)
if(checkCompany){
company.set(urlId)
}
else{
navigate("/404")
}
I have hooks in place where in if company.set(<company:id>) does update, it will pre-fetch everything else needed for the view. And, company is a custom context hook so that it's present everywhere in my application.
Is there a better way in handling this? It seems hack-y to manually check the path name.
You can assume that my gatsby_node.js has the right definitions to allow the client side routing.
Here's my routing definitions: (this is what i put in the pages folder)
const DashboardPage = () => (
<ProtectedRoute>
<Router>
<Company path="/company/*" />
</Router>
</ProtectedRoute>
)
Finally in the components folder,
const Company = ({location}) => (
<Router>
<Main path="/:companyId">
<Summary path="/" />
.... other dashboard routes
</Main>
</Router>
)
You have to assume that client-side code can always be changed by a malicious actor. Ultimately, you have to make sure on the backend that a user can only request ressources he is supposed to see.
Your solution for the client-side seems fine to me. I don't see another way than checking the URL path manually and then redirecting.
By logging in, your user needs to be assigned to a cryptographically safe cookie or token (such as JSON web tokens) so you can always be sure of their identity. Everytime a company id is routed to, your frontend needs to send the user identity to your backend. Only there you can be safe from code manipulations. Your backend needs to check if the user can look at this page.
If the user can: your backend sends the page data
If the user can't: your backend sends "not authorized" message and your frontend redirects
This way even if someone manipulates your client-side code and cancels the redirect, the hacker will stare at a useless blank page.
In summary:
Your approach on the client-side is fine. Make sure your backend checks the identity of your user before sending the company data.
So instead of me manually handling the routing, I solely used ReachRouter's navigate to simulate history.push() to avoid re-rendering of the whole page. (similarly just a state change with the benefit of keeping track of history ). Backend is fully protected with auth tokens so no need to worry about that.
My strategy below will handle these cases:
User types in or app navigates to /company ( app will pre fetch > get first company by default > navigate to /company/{id}
User types in or app navigates to /company/{id} ( app will pre-fetch > navigate to /company/{id} > component triggered by that route will check validity of id else navigate to 404 )
The strategy I created was,
Create a component that will load up a loading screen by default and prefetch the said companies.
Call the said component in the pages folder as the default, in my case, /company should be pages > company.js or pages > company > index.js
If the prefetch is successful, navigate to /company/{id} which is the child of the said component, which is a purely client route.
make sure gatsby-node.js have the necessary createPages definition to allow client routing for everything /company/*
manually check if the current location is /company to deny redirection to capture the second case when user types in /company/{id}
Better if I show the code, ignore my custom built in hooks.
useRequest just gives an axios class, with the true parameter telling it's an authenticated required request.
useApi just gives a handy (async-await) function that includes dispatch for the redux states and calling the api itself. I'm using thunk, so success and error are standard.
const CompanyDefault = ({location}) => {
const [loading, toggle] = useState(true)
const request = useRequest(true)
const companyApi = useApi(request, getCompanies)
const companyState = useSelector(({company}) => company)
const redirectToCompany = async () => await navigate(clientRoutes.COMPANY(companyState.data[0].id))
const fetchCompanies = async () => {
await companyApi()
toggle(false)
}
useEffect(() => {
if(companyState.data.length === 0){
fetchCompanies()
}
}, [])
if(loading){
return <Loading />
}
else if(companyState.error || companyState.data.length === 0){
return <Error callApi={companyApi} />
}
else if(companyState.success && (location.pathname === "/company" || location.pathname === "/company/")){
redirectToCompany()
}
return (
<Router>
<Company path="company/:companyId/*" />
</Router>
)
}
export default CompanyDefault
Thus, the company module will be just
const Company = ({companyId}) => (
// you now have companyId!
// do magic here, and check if that company id is valid.
<Router>
<Main path="/">
<Summary path="/" />
.... other dashboard routes
</Main>
</Router>
)
Hopefully this is cleaner. If there's a better way do let me know! :D

How to Handle Post Request in Isomorphic React + React Router Application

I want to build Isomorphic react + react-router application and after a few days googling, now I can achieve isomorphic application that only handles GET request.
Here's what I've done so far:
Server use react-router to handle all request
react-router will call fetchData functions that resides in each React View that matches the route.
Set the data fetched before into props of the React View and render it into string
Inject the string and data fetched before as global variable window.__STATE__ into HTML and deliver the HTML to the client
We have successfully render React App from the server
When the client finished loading our React App javascript, it will try to render. But we pass the state from window.__STATE__ as the props of our React App, and React will not re-render because the state is the same
The problem is it will not work with POST/PUT/DELETE/WHATEVER request. When handling GET request, react-router have information about params and query. For example if we have a route: /user/:uid and client request this url: /user/1?foo=bar, then params would be: {uid: 1} and query would be {foo: 'bar'}
react-router then can pass it down to fetchData function so it will know to fetch user with uid of 1 and do whatever with foo query.
While in POST request, react-router doesn't know about the POST parameters. On Server, of course we could pass the POST parameters to fetchData function, but what about the Client? It doesn't know what the POST parameters are.
Is there a way that the server could tell the Client about the POST parameters? Below is an example of my Login View. I want when user submit the form, the server will render error message on error, or redirect it to dashboard on success.
fetchData.js
import whenKeys from 'when/keys';
export default (authToken, routerState) => {
var promises = routerState.routes.filter((match) => {
return match.handler.fetchData;
}).reduce((promises, match) => {
promises[match.name] = match.handler.fetchData(authToken, routerState.params, routerState.query);
return promises;
}, {});
return whenKeys.all(promises);
}
server.js
...
app.use((req, res) => {
const router = Router.create({
routes,
location: req.originalUrl,
onError: next,
onAbort: (abortReason) => {
next(abortReason);
}
});
router.run((Handler, state) => {
fetchData(authToken, state).then((data) => {
// render matched react View and generate the HTML
// ...
})
});
});
...
login.jsx
import React from 'react';
import DocumentTitle from 'react-document-title';
import api from './api';
export default class Login extends React.Component {
constructor(props) {
super(props);
// how to fill this state with POST parameters on error?
// how to redirect on success?
// and remember that this file will be called both from server and client
this.state = {
error: '',
username: '',
password: ''
};
}
// I saw some people use this function, but it'll only work if
// the form's method is GET
static willTransitionTo(transition, params, query) {
// if only we could read POST parameters here
// we could do something like this
transition.wait(
api.post('/doLogin', postParams).then((data) => {
transition.redirect(`/dashboard`);
});
);
}
render() {
return (
<DocumentTitle title="Login">
<div className="alert alert-danger">{this.state.error}</div>
<form method="post">
<input type="text" name="username" value={this.state.username} onChange={this._onFieldChange('username')} placeholder="Username" /><br />
<input type="password" name="password" value={this.state.password} onChange={this._onFieldChange('password')} placeholder="Password" /><br />
<button type="submit">Login</button>
</form>
</DocumentTitle>
);
}
_onFieldChange(name) {
var self = this;
return (e) => {
e.preventDefault();
var nextState = {};
nextState[name] = e.target.value;
self.setState(nextState);
}
}
}
Getting "POST" data on the client
On the client side, you get POST data by extracting values from your form inputs in a way which corresponds to what you would have received on the server had the form been submitted normally.
Using POST data
So now you have your POST data, but you still have the problem that there's no way to feed the POST data into your transition hooks in React Router 0.13.x and earlier. I created a pull request for this feature which has now been closed because it was included as part of the rewrite for the upcoming v1.0 release.
The gist of it is that locations now have a state object for squireling away any extra data you need about the current request/transition (the two are analagous) being handled:
On the server, you're dealing with one request at a time, so you create a static Location with data from req.body
On the client you pass the state object (containing extracted form data) to transitionTo().
Now your transition hook is capable of receiving the same form data in both environments. If things go well, great! If things don't go well, you need a way to pass errors and re-render the form again. New state object to the rescue again! Use transition.redirect() and pass both input data and errors and you now have everything you need to render on both sides.
I'm not going into more specific detail right now because v1.0 is still in beta and v0.13.x doesn't have the necessary API to do this anyway, but I have a repository which uses the pull request above to implement this workflow with 0.13.x which you could look at in the meantime:
isomorphic-lab - the README gives an overview of how things fit together.
Here are some rough flow diagrams of the process, too:
Server POST with errors and redisplay
Client POST with errors and redisplay
I've also created a few reusable modules related to this scenario:
get-form-data gets data from a form's inputs in the format it would have been POSTed in.
react-auto-form provides <AutoForm>, which you can use instead of <form> to receive all the data from a form's inputs as an argument to its onSubmit handler
react-router-form, which is to <form> what React Router's <Link> is to <a> - it handles triggering a transition to the given action, passing method and body (form data) state - this will be updated for v1.0 soon.

Categories