The essential boilerplate to authenticate users on your React-Native app
It’s essential to many apps, and I myself have been wondering how I could provide a secure way for my users to register and authenticate to my apps without third-party strategies. Finding working examples online has been tough, so I have decided to implement my own based on the few examples I could find.
The main goal of this article is to show you how to create and setup a few useful services to improve the life cycle of your app, to authenticate a user and access protected resources.
- I will be demonstrating the authentication using a simplified version of OAuth2, with a long-live refresh token and short-live access token. To simplify the example, the Resource server and the Authorization server are the same entity. If you want to know more about mobile security, I suggest you read this article: https://stormpath.com/blog/the-ultimate-guide-to-mobile-api-security
- The scope of the project is generic enough to let anyone adapt it to their own need. You might need to make changes in order to get everything production ready.
For this article I have set up a working example of a React-Native client (works for iOS and Android) and a NodeJS server on Github. Please follow the instructions from the README file to install and run the project.
Contribute to react-native-authentication development by creating an account on GitHub.
The client has non-protected and protected scenes. The non-protected scenes are the ones allowing the user to login and register. Once the user is logged-in (or registered), the client receives an access token and a refresh token, and gets transitioned to the protected scene.
The access token is a credential valid for 1 hour, used to access the protected content from the server API. When it expires, the client uses the refresh token to obtain a new access token. The refresh token is valid for 90 days, after which the user will have to manually login again.
The server is similar to the client, and has non-protected and protected entry points. The non-protected entry points require a Client ID to be sent for each requests. This ID needs to be generated ahead of time, and is sent to make sure the client is legitimate. The protected entry point requires an access token, which can be obtained by login-in.
Server API entry-points
In order for the client to be authorized to access the API you must generate a Client ID first. For the purpose of this project an open entry-point is provided to generate this ID. This should not be done in production:
The Client ID you get needs to be hard-coded into the React-Native application, and sent to the server as a
Header parameter for each request to any non-protected entry-points.
The non-protected entry-points allow authentication and registration:
POST /users: Create a new user
POST /users/auth: Authenticate and retrieve the access and refresh tokens in exchange of email/password
POST /users/auth/refresh: Authenticate and retrieve the access token in exchange of the refresh token.
The server requires the client to send the user access token in order to make a request to the protected entry-point.
The protected entry-point allows everything else:
GET /users: Retrieve the list of users
POST /users/auth/revoke: Log out, revoke access by destroying the user tokens
Setup your React-Native application
The application I have setup on Github uses a feature-based architecture. I would recommend you use a similar architecture to have a better organized project. I give more details about this architecture in another Medium article I wrote:
How to better organize your React applications?
I’ve been working on very large web applications for the past few years, starting from ground zero and, with a dozen…
As a reminder, this is how it’s designed:
components: global components that can be used anywhere in your application.
data: takes care of fetching and saving the data on the Redux store as needed. The
scenesare usually the ones consuming
scenes: your application scenes
services: self-contained modules “helpers” that cannot be assimilated as components.
App.jsis the bootstrap of the application. It will by default load the
Splashscene and try to auto-login the user. Then, transition to the right scene.
- Finally, the
store.jsimports the reducers and creates the Redux store. It exports a singleton that can be used anywhere in the client.
The interface of the app is very simple. The user can access the Register or Login scenes from the Welcome scene. Once registered or logged-in, the user is transitioned to the Users scene. From there, the user can Log-out, and will be transitioned back to the Welcome scene.
Interacting with the API
The server API expects to receive a few mandatory headers for each request the client will make. It expect you to send JSON content, and returns JSON responses. Those headers are required for all requests:
We also have 2 different situations.
- For non-protected resources the API expects the client to send the
Client-IDas part of the header.
- For protected resources the API expects the client to send the
Authorizationheader which will have the access token as a Bearer.
Of course, you don’t want to manually add those headers for every individual request you make to the server. You can create a service that will do this for you. Let’s call it the API service.
The first thing you need to do is to create a config file where you hard-code the Client ID and the URL of the server API.
I recommend you enhance this config file, as you have to use a different URL per environment, for example.
fetch() in order to make network requests, similar to XMLHttpRequest. You can learn more about it on the React-Native documentation. I use
fetchival() to simplify the use of
(See repo here).
We need to inject the required headers and manage the server API errors. To do that, you need to create a function that will be used for all future API requests the client will make. This function accesses the user access token and the Client-ID, and adds them to the headers as needed, so you don’t have to manually add them for each request. (see next section to learn more about session).
Now you can just import this service and use the
fetchApi() function for all the requests your app will make to the server API.
But you would think I should maybe create a Redux middleware to make the network calls to the server API? Keep reading.
Create a new user
The creation of users is not related to the authentication and does not impact the Redux store. There is no need to save the error coming from the server API in the Redux store either, as only the scene calling the
create method will consume this error. I’ve seen developers in many projects having all the network requests made from the Redux
actions.js file, suggesting that every call will have an impact on the Redux store. But this is not true. That is why I don’t use a Redux middleware.
I recommend you to create a file called
api.js in charge of each network request, and you import the functions wherever you need them.
Authenticate and keep the user logged-in
When you work with an app, you need to keep information about the user who requests content from the server API. The session service provides functions to authenticate and revoke user access, has its own actions and reducer to save the tokens in the Redux store and takes care of refreshing the access token automatically.
To authenticate the user, the server expects the client to send credentials via Basic Auth, transforming the email and password and adding it to the headers as Basic Access.
Once authenticated, the tokens get saved to the Redux store, and are kept on the device local storage and restored the next time the user opens the app.
A timeout is also created to automatically refresh the access token when it’s about to expire. This keeps the user logged-in when the app is open.
Logging out the user will revoke its tokens and clear them from the Redux store.
Bring everything together
Now we’re ready to link the scenes to the different functions we’ve created.
The first thing the app does is wait for the Redux store to be restored from the last session. redux-persist allows us to save the Redux store in the device local storage and restore it later. If you wish to add a layer of security, you can also use redux-persist-transform-encrypt in order to encrypt the Redux store when saved in the device local storage. Thanks Rob Moorman for recommending this.
Once the state is ready it attempts to refresh the access token, if there is a refresh token available in the store. This is how we can auto-login the user.
If the access token is properly refreshed, the splash screen is removed and the user gets transitioned directly to the Users scene. Otherwise, the user gets transitioned to the Welcome scene.
The user enters their email address and password which get saved in the state of the scene. Pressing the Login button will call the
authenticate function from the session service. If it succeeds the client transitions the user to the Users scene, otherwise, the error is extracted and displayed in the scene.
Simply call the
create function from the
api.js file created before.
Once a user is successfully created it gets authenticated and transitioned to the Users scene.
Revoke the user tokens and transition the user to the Welcome scene.
That’s it! You can now register and authenticate your users on your React-Native mobile app. I didn’t cover every single thing but you can access the code from Github to get to understand how everything works together.
This project is just one of many ways you can implement the authentication for your React-Native app. You can use this project as a bootstrap and adapt it for the app you want to build.
Feel free to comment below or contact me directly if you have any questions, I’ll be more than happy to help.
More articles from me
- How to better organize your React applications?
- What are the main differences between ReactJS and React-Native?