React Native is a mobile app framework from Facebook. It allows you to quickly develop apps using React's API and deploy them to iOS and Android. It allows you to quickly refresh the apps when you make changes and generally offers a pleasant experience for web developers.
React Native for Web is a recent addition to the React Native family. It allows you to run your app in a browser and enjoy the browser's built-in dev tools. It's optimized for mobile and uses the same components as React Native, so it looks good too!
In this tutorial, I'll show you how to create a React Native application that deploys to the web, iOS, and Android. You'll configure it to use OpenID Connect (OIDC) for authentication and use OAuth 2.0 access tokens to talk securely to a Spring Boot API.
Below is a diagram of the app you'll create in this tutorial and its authentication flow.
Prerequisites:
If you're on Windows, you may need to install the Windows Subsystem for Linux for some commands to work.
I recommend using SDKMAN to manage your OpenJDK installations. Just run sdk install java 11.0.2-open
to install Java 11 and sdk install java 17-open
for Java 17.
Quick Apps with JHipster
JHipster is a full-stack application generator that uses Spring Boot on the backend and Angular, React, or Vue on the front end. It started as an open-source project in 2013 before any of these frameworks existed. It then used Spring MVC for the backend and AngularJS for the front end.
Its popularity grew quickly along with its adoption and rise of the frameworks above. Today, it averages over 100K downloads per month. It even has a healthy budget, thanks to its sponsors and backers via Open Collective.
Why am I telling you this? Because JHipster will help you build your React Native app and it was used to create the backend in a previous tutorial.
JHipster Blueprints for More Power!
JHipster added a blueprints feature several years ago. It allows you, as a developer, to create a project that overrides the default behavior of JHipster. This feature has led to a thriving ecosystem of blueprints, from Kotlin to Micronaut to .NET Core to NestJS to Svelte to Ionic and even React Native.
Today, I'll show you how to use JHipster's React Native blueprint to build a Flickr clone. The backend will be powered by the Spring Boot app created in Full Stack Java with React, Spring Boot, and JHipster tutorial. The React Native app's screens will be generated from the same JDL (JHipster Domain Language) file that created the backend entities.
I'm excited to show you how to use JHipster React Native because its 4.3.0 release upgrades it Expo 46, React Native 0.69.5, and React 18. Let's giddyup! 🤠
Build a Photo Gallery with React and Spring Boot
Start by creating a new directory to hold your frontend and backend projects:
take react-native-spring-boot
NOTE:
take
is a command that makes a directory and moves into it.
Clone an existing JHipster app into a backend
directory:
git clone https://github.com/oktadev/auth0-full-stack-java-example.git backend
This app is configured to use OIDC for authentication and needs a provider configured to start correctly. To make things quick, a pre-configured Keycloak instance is configured with a Docker Compose file. You can start it:
cd backend
docker-compose -f src/main/docker/keycloak.yml up -d
Apple Silicon and Keycloak
If you're using Apple Silicon (aka M1 or M2), Keycloak will fail to start because its Docker image wasn't available for an M1 when the backend project was created. To fix it, run the script below.
VERSION=15.0.2 # Keycloak version specified in keycloak.yml
cd /tmp
git clone git@github.com:keycloak/keycloak-containers.git
cd keycloak-containers/server
git checkout $VERSION
docker build -t "jboss/keycloak:${VERSION}".
docker build -t "quay.io/keycloak/keycloak:${VERSION}".
Then, start the backend using ./mvnw
and open your favorite browser to http://localhost:8080
. You should be able to log in with admin/admin
and upload photos. They'll be displayed in a nice grid, and you can click each photo to zoom in.
Now let's create a React Native app that talks to the same API.
Generate a React Native App
Install React Native JHipster and the Expo CLI:
npm install -g generator-jhipster-react-native expo-cli
Create a directory for your React Native app:
take mobile
Run the following command to use the React Native blueprint to create an app.
jhipster --blueprints react-native # you can also use `rnhipster`
When prompted, use the following values:
Prompt | Answer |
---|---|
What do you want to name your React Native application? | Flickr2 |
Enter the directory where your JHipster app is located: | ../backend |
Do you want to enable end-to-end tests with Detox? | No |
Next, generate screens based on the entities in the backend project. Press a (for all) when prompted to overwrite files.
rnhipster jdl ../backend/flickr2.jdl
In the backend project, change its src/main/resources/config/application-dev.yml
to allow http://localhost:19006
for CORS (cross-origin resource sharing):
cors:
allowed-origins: 'http://localhost:19006,...'
Sign up for an Expo account and take note of your username.
Log in to Keycloak (with admin/admin
as credentials). Navigate to Clients > web_app and add https://auth.expo.io/@<your-expo-username>/Flickr2
as a Valid Redirect URI. Save your changes.
Hide the metadata (height, width, date taken, and date uploaded) for photos in the add photo screen (mobile/app/modules/entities/photo/photo-edit-screen.js
) when uploading a new photo. This isn't necessary, but the backend calculates these values for you, so they won't be saved. Below are the changes you need to make.
const metadata = (
<View>
// move the form fields for height, width, taken, and uploaded here
</View>
)
const metadataRows = isNewEntity ? '' : metadata;
// Replace the form fields you moved with the following
{metadataRows}
Click here to see what it looks like from a diff perspective.
diff --git a/mobile/app/modules/entities/photo/photo-edit-screen.js b/mobile/app/modules/entities/photo/photo-edit-screen.js
index 7a74a97..8aba557 100644
--- a/mobile/app/modules/entities/photo/photo-edit-screen.js
+++ b/mobile/app/modules/entities/photo/photo-edit-screen.js
@@ -97,6 +97,48 @@ function PhotoEditScreen(props) {
const albumRef = createRef();
const tagsRef = createRef();
+ const metadata = (
+ <View>
+ <FormField
+ name="height"
+ ref={heightRef}
+ label="Height"
+ placeholder="Enter Height"
+ testID="heightInput"
+ inputType="number"
+ onSubmitEditing={() => widthRef.current?.focus()}
+ />
+ <FormField
+ name="width"
+ ref={widthRef}
+ label="Width"
+ placeholder="Enter Width"
+ testID="widthInput"
+ inputType="number"
+ onSubmitEditing={() => takenRef.current?.focus()}
+ />
+ <FormField
+ name="taken"
+ ref={takenRef}
+ label="Taken"
+ placeholder="Enter Taken"
+ testID="takenInput"
+ inputType="datetime"
+ onSubmitEditing={() => uploadedRef.current?.focus()}
+ />
+ <FormField
+ name="uploaded"
+ ref={uploadedRef}
+ label="Uploaded"
+ placeholder="Enter Uploaded"
+ testID="uploadedInput"
+ inputType="datetime"
+ />
+ </View>
+ );
+
+ const metadataRows = isNewEntity ? '' : metadata;
+
return (
<View style={styles.container}>
<KeyboardAwareScrollView
@@ -145,41 +187,7 @@ function PhotoEditScreen(props) {
autoCapitalize="none"
onSubmitEditing={() => heightRef.current?.focus()}
/>
- <FormField
- name="height"
- ref={heightRef}
- label="Height"
- placeholder="Enter Height"
- testID="heightInput"
- inputType="number"
- onSubmitEditing={() => widthRef.current?.focus()}
- />
- <FormField
- name="width"
- ref={widthRef}
- label="Width"
- placeholder="Enter Width"
- testID="widthInput"
- inputType="number"
- onSubmitEditing={() => takenRef.current?.focus()}
- />
- <FormField
- name="taken"
- ref={takenRef}
- label="Taken"
- placeholder="Enter Taken"
- testID="takenInput"
- inputType="datetime"
- onSubmitEditing={() => uploadedRef.current?.focus()}
- />
- <FormField
- name="uploaded"
- ref={uploadedRef}
- label="Uploaded"
- placeholder="Enter Uploaded"
- testID="uploadedInput"
- inputType="datetime"
- />
+ {metadataRows}
<FormField
name="album"
inputType="select-one"
Run Your React Native App
If the backend app isn't running, open a terminal and navigate to the backend
directory. Then, run ./mvnw
(or mvnw
on Windows). Of course, if you have Maven installed, you can simply run mvn
.
Open a new terminal window and navigate into the mobile
directory. Run npm start
and type w to open in a web browser. You should be able to log in and view any photos you added to the backend. You can even edit and replace them.
Test on iOS
To see your React Native app running on iOS, press i in the window you ran npm start
from. You will need to be on a Mac with Xcode installed for this to work.
💡 TIP: You can reload your app in Simulator using commandKey ⌘ + R.
Test on Android
To see your React Native app running on Android, press a in the window you ran npm start
from. You will need Android Studio and an AVD (Android Virtual Device) running. I tested on a Pixel 5 with API 31 (Android 12.0).
For the Android emulator to communicate with your API and Keycloak, you'll need to add some port mappings. You'll know the command worked if 8080
and 9080
are printed to your terminal.
adb reverse tcp:8080 tcp:8080 && adb reverse tcp:9080 tcp:9080
⚠️ CAUTION: If you get a
command not found
error, see this Stack Overflow Q & A to solve it. I usedecho export "PATH=~/Library/Android/sdk/platform-tools:$PATH" >> ~/.zshrc
on my Mac.
To reload your app, hit r
twice with a focus on the Android emulator.
Use Auth0 for Identity
JHipster ships with Keycloak when you choose OAuth 2.0 / OIDC as the authentication type. However, you can easily change it to another identity provider, like Auth0!
First, you'll need to configure the backend to use Auth0 by registering a regular web application. Log in to your Auth0 account (or sign up if you don't have an account). You should have a unique domain like dev-xxx.us.auth0.com
.
Select Create Application in the Applications section. Use a name like JHipster Baby!
, select Regular Web Applications
, and click Create.
Switch to the Settings tab and configure your application settings:
- Allowed Callback URLs:
http://localhost:8080/login/oauth2/code/oidc
- Allowed Logout URLs:
http://localhost:8080/
Scroll to the bottom and click Save Changes.
In the roles section, create new roles named ROLE_ADMIN
and ROLE_USER
.
Create a new user account in the users section. Click the Role tab to assign the roles you just created to the new account.
Make sure your new user's email is verified before attempting to log in!
Next, head to Actions > Flows and select Login. Create a new action named Add Roles
and use the default trigger and runtime. Change the onExecutePostLogin
handler to be as follows:
exports.onExecutePostLogin = async (event, api) => {
const namespace = 'https://www.jhipster.tech';
if (event.authorization) {
api.idToken.setCustomClaim('preferred_username', event.user.email);
api.idToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
api.accessToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
}
}
This code adds the user's roles to a custom claim (prefixed with https://www.jhipster.tech/roles
). This claim is mapped to Spring Security authorities in SecurityUtils.java
on the backend.
Select Deploy and drag the Add Roles
action to your Login flow. Create a backend/.auth0.env
file and populate it with your Auth0 settings.
export SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=https://<your-auth0-domain>/
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=<your-client-id>
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=<your-client-secret>
export JHIPSTER_SECURITY_OAUTH2_AUDIENCE=https://<your-auth0-domain>/api/v2/
ℹ️ NOTE: Want to have all these steps automated for you? Watch issue #351 in the Auth0 CLI project.
Stop your backend app with Ctrl + C and run the following commands to configure Spring Security to use Auth0.
source .auth0.env
./mvnw
Create a native OIDC app for React Native
For the React Native app to use Auth0, you'll need to create a Native app and add the following Allowed Callback URLs:
http://localhost:19006/,https://auth.expo.io/@<your-expo-username>/Flickr2
Configure Allowed Logout URLs:
http://localhost:19006,https://auth.expo.io/@<your-expo-username>/Flickr2
And, set the Allowed Origins (CORS):
http://localhost:19006,http://localhost
The second value is the origin header that Android sends. Copy the client ID to app/config/app-config.js
and update the audience
in app/modules/login/login.utils.ts
:
audience: 'https://<your-auth0-domain>/api/v2/',
Restart your React Native app and log in with Auth0!
It works on Android too. 🥳
Use Okta for identity
If you'd like to use Okta as your identity provider, see JHipster's documentation for configuring the backend app.
💡 TIP: You can configure JHipster quickly with the Okta CLI:
okta apps create jhipster
You'll need to create a native app on Okta for React Native too.
Log out from Your Identity Provider
You probably didn't notice, but if you log in to your app when it's running on iOS or Android, then log out, when you try to log in again, you aren't prompted for credentials. This is because the React Native blueprint configures the best developer experience. It's kind of a pain to enter your credentials each time on a mobile device. Also, Expo's auth proxy does not currently work with logging out from the identity provider. If you look at app/config/app-config.js
, you'll see that only web
disables the auth proxy.
useExpoAuthProxy: Platform.select({ web: false, default: true }),
If you want to sign out on native apps completely, change the value to false
.
useExpoAuthProxy: false,
Disabling the auth proxy will cause your app's redirect URIs to change. You'll need to update your identity provider to add the following to your login and logout URLs:
exp://<your-ip-address>:19000 # e.g., exp://172.20.10.4:19000
💡 TIP: You can open your Auth0 app quickly with the Auth0 CLI:
auth0 apps open
After making these changes, reload your app. On iOS, it'll show a permission dialog when you try to log out.
Unfortunately, this is part of iOS and not something that can be suppressed. On the upside, your users probably don't want to log out fully. You haven't logged out of Gmail recently, have you?
Deploy to Production
The React Native project is configured to work with Expo Application Services (EAS) Build. To use it, you'll need to install the EAS CLI:
npm install -g eas-cli
Then, log in to your Expo account:
eas login
And configure your project:
eas build:configure
For more information, see Creating your first build docs. To learn how to deploy to production and make your app available in app stores, explore EAS Deployment patterns.
Learn More about React Native, Spring Boot, and JHipster
I hope you enjoyed this quick tour of securely integrating a Spring Boot backend with a React Native frontend. JHipster generated most of the code, leaving you more time to implement your custom business logic. It's nice that OIDC authentication is supported out-of-the-box. It works so smoothly with Keycloak, Auth0, and Okta!
You can find the source code for this example on GitHub in the @oktadev/auth0-react-native-jhipster-example repository.
If you liked this post, you might find these resources helpful:
- Auth0's React Native QuickStart
- Get Started with Auth0 Authentication in React Native Android Apps
- Get Started with Auth0 Authentication in React Native iOS Apps
- Build Secure Ionic Apps with Angular and JHipster
- JHipster React Native Demo
- Create a React Native App with Login in 10 Minutes
Please follow me at @mraible on Twitter. Follow my team at @oktadev and subscribe to our YouTube channel. Please comment below if you have any questions or suggestions for future tutorials.