On this page:
You should protect your React app at two different layers:
Protecting the data is really the key one. If there is a bug in your React app, and a user can navigate to a view that he or she should not be able to access, the API should not return any data. In other words, if authorization failed at the React layer but authorization didn’t fail at the API level, then the user will only see a screen with no data.
In this article we are going to explain how to secure both layers focusing on the client-side of the application.
Either your app uses REST or GraphQL for the API, both cases work over the HTTP protocol. This means that your API is stateless at the protocol layer.
If you want to authorize users first you need to keep track of them. There are two approaches in a client/ server application to keep the state of the session:
There is no one solution that fits all. I always say that we, developers, are paid to make decisions, not to use tools that we know or find cool. In most applications we judge the simplicity we gain from using stateless servers overcomes the control we lose - but this will depend on your particular scenario.
So how does this client-side state work? Cryptography. The client stores the state, but it’s the server who has the (private) key to validate the session the client claims to be. For this we are going to use JWT.
JWT (JSON Web Token) is an open, industry standard RFC 7519 method for representing claims securely between two parties.
We are not going to get into much detail about JWT in this article, but we want to make sure a few things are pretty clear regarding these JSON Web Tokens:
The JWT website has a debugger tool on the home page and you can paste tokens and visualize the content. Have a look, it’s fun to play with it if you haven’t yet.
OK we go for JWT, now the next question is “where do I store the token on the client”? There are typically two places on your web app you can use i) local storage or ii) cookies. If you can (meaning the server and the client are in the same domain/ subdomain) I recommend to use cookies. The reason is that your application won’t have to manage the token. The token will be managed at the browser level, so it makes it more difficult to create a bug that can potentially open some security vulnerability.
In the end, where you store the token is an implementation detail. In our React training we always use cookies also for the sake of simplicity. Since the browser takes care of it, we implement less code and so move faster through the curriculum (which is quite dense!).
First part, protecting the data, done! What did we have to implement on our React app for this? Nothing. The API will set a cookie, and the API will read and verify the cookie on every request before returning or manipulating any data. We told you it was going to be fast. The token is managed by the browser not by your React app.
Authentication is the act of matching a session with a given user. Example, given a session I can securely identify the user is user_id 123.
Authorization is the act determining if a given user can access a given resource. Example, given two users, an admin and a super admin; admins are authorized to see invoices but can’t delete them; super admins are authorized to see and delete invoices.
Obviously authentication must happen before authorization.
Authentication starts by having a mechanism for users to get a valid token they can use when accessing different resources in the app. Typically this will be a form that will submit the credentials (e.g. username and password) to an API. You can see how to create a React login form in this article https://reactgraphql.academy/react/react-forms-controlled-and-uncontrolled-components/.
This part is about deciding in your React code what views a given session can access to. As we discussed, we secure the data properly at the API layer, therefore the React layer does not add extra security but better user experience navigating the site.
There is mainly one task your React app needs to take care of, to stop the user from navigating a given path if the current session (or lack of it) can’t access to it.
Example, given a website with public and private routes, if the user is not authenticated then redirect the user to the login page otherwise display the App.
You can implement that easily by adding a condition at the Root level in the component tree making the App branch (left branch) render only if the condition is true (e.g there is a valid token)
The following is an example of how to implement that conditional rendering in your React component
In the previous example we’ve seen the Redirect component. The Redirect component is a declarative way to take the user to a different path. Internally the Redirect component is doing “history.push(path)”. The React component encapsulates some imperative code so we can be declarative. Declarative is the prefered choice in React so you should try to avoid using history.push(path).
If we use a cookie to store the JWT, then the getSession function implementation in that example could be this
import Cookies from 'js-cookie' export const getSession = () => { const jwt = Cookies.get('__user') let session try { if (jwt) { const base64Url = jwt.split('.')[1] const base64 = base64Url.replace('-', '+').replace('_', '/') // what is window.atob ? // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob session = JSON.parse(window.atob(base64)) } } catch (error) { console.log(error) } return session } export const logOut = () => { Cookies.remove('__user') }
If your application has different routes that can be public or private, you can create a generic component that will take care of redirecting as we did in the previous example. This is an example from the React Router official documentation:
function PrivateRoute({ component: Component, ...rest }) { return ( <Route {...rest} render={props => fakeAuth.isAuthenticated ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: "/login", state: { from: props.location } }} /> ) } /> ); }
You can use the PrivateRoute to redirect the user to the login page for any path the user must be logged in. Here is an example:
import { Switch, Route } from "react-router-dom" import ProtectedRoute from "./ProtectedRoute" // other required imports like Auth, Onboarding, etc // some component wrapping this return ... return ( <Switch> <Route path="/auth" component={Auth} /> <Route path="/onboarding" component={Onboarding} /> <ProtectedRoute path="/customer-error/"} component={ExistingCustomerError} /> <ProtectedRoute path="/discrete-loan" component={DiscreteLoan} /> <Route component={NotFound} /> <Switch> )
We won't spam you as per our Privacy Policy.
Share this on:
Comments? Shoot me a tweet @alex_lobera !
GraphQL Evening with Round Table 💥 Online
London, UK
Prices & more details