Get the user id configured for the device (iCloud recordID for iOS, email of a device account for android)
When implementing a mobile application you want to provide your users as much functionality as you can without having them sign up. After all, Every Step Costs You 20% of Users.
Sign up may very well be the step where most of your potential users are lost.
Now imagine you could get a unique identifier that worked across all of the devices of the user. You could even have in-app purchases that are shared across devices without having to sign-up.
This library aims to provide all of the above in the simplest way possible:
- For ios it uses the record name of CloudKit so it uniquely identifies an ios user. It's difficult to imagine an apple user that does not have an iCloud account configured in each of their devices.
- For android it presents to the user a list of all the accounts configured in the device to choose from.
It presents the list of google accounts configured (...@gmail, ...@corporateGmail) since it's one of the first steps of configuring an android device nowadays.
Note: If you are already using firebase authentication see the FAQ for instructions on how to upgrade its anonymous authentication
Note: If you are considering using Sign In with Apple see the FAQ section first
This package requires you to activate the iCloud entitlement on iOS.
As of today apple does not permit transfering ownership of an application that has this entitlement active for ANY of it's versions. Read more here
If you want to transfer your app in the future and you still want to use this package consider using an apple developer account exclusively for the app, and when the time comes transfer the account.
A personal apple developer account can be changed to a corporate account if needed, as is most of its information.
1. Install the library
yarn add react-native-user-identity
2. Run pod install
cd ios
pod install
3. Configure swift support for your iOS project
This configures your iOS project to use the module correctly since the library contains swift code (see the official react native documentation for more information)
Note: If your iOS project is alredy using swift files and you have a bridging header you may skip this step.
a. Create a swift file with any name in the root of your project:
b. Select "Create Bridging Header" when Xcode asks for it:
4. Build the project
You should be able to build the project now.
Note: The package functionality will not work until you follow the steps of the next section
Note 2: If you are having trouble building your project after executing the example try opening XCode preferences, select the "Locations" tab and set DerivedData to be a relative folder.
Nothing to configure.
(Screenshots taken with Xcode 11.x)
- Make sure your app has a unique bundle identifier and does not show any errors in the Signing section (requires an apple development account):
- Add the iCloud capability for the project:
- Enable the cloudkit option, add a container and name it exactly as the bundle identifier of the application:
- Verify the configuration:
Verify all of the following:
- The format of the container name is iCloud.$(PRODUCT_BUNDLE_IDENTIFIER)
- Press the "Cloudkit Dashboard" button, sign in with your developer account. You should see your newly created container in the list of containers (If you don't see it go to XCode and press the refresh button until it does). Verify that there is no error when selecting your container in the web dashboard.
If there is an error just wait and keep trying until the container is created succesfully (web dashboard/Xcode 11 seems to be buggy here since creating an iCloud container actually takes a couple of minutes). - Go back to XCode and verify that the container name is not highlighted in red after you press the refresh option
- There is only one public function available called getUserId
- The function is marked as async
- The resolved value is null when the user cancels the UI flow
- On ios the function will throw ICLOUD_ACCESS_ERROR when there is no icloud account configured
import RNUserIdentity, { ICLOUD_ACCESS_ERROR } from 'react-native-user-identity'
fetchUserIdentity = async () => {
try {
const result = await RNUserIdentity.getUserId()
if (result === null) {
alert('User canceled UI flow')
}
} catch(error) {
if (error === ICLOUD_ACCESS_ERROR) {
alert('Please set up an iCloud account in settings')
}
}
}
On iOS fetching the id does not require user intervention. However, it might be useful in some instances to have the user confirm the action.
You may send a truthy value for the iosUserConfirmation parameter for this to happen.
The following code:
RNUserIdentity.getUserId({
iosUserConfirmation: true
})
Presents this dialog:
The resolved value will be null if the user dismisses the dialog
You can also configure the text shown to the user:
RNUserIdentity.getUserId({
iosUserConfirmation: {
title: 'Confirm sign in',
message: 'Sign in requires user confirmation',
signInButtonText: 'Confirm',
cancelButtonText: 'Back'
}
})
There is an optional parameter you can send:
- androidAccountSelectionMessage: The text to display in the account chooser modal in android. By default there is no message configured.
The following code:
RNUserIdentity.getUserId({
androidAccountSelectionMessage: 'Choose an account for testing:'
})
Presents this modal (the modal styles are OS dependant):
Use yarn to install the dependencies. Npm installs local dependencies using symbolic links and the react native packager does not support this.
The CloudKit framework prevents applications from accesing the user email for privacy purposes.
Sign in with Apple requires the user to complete a full sign in flow.
The point of using this package is to skip entirely this flow so your users can directly start using your application
Furthermore, activating sign in with Apple also prevents your app from being transferable (see the Why shouldn't you use this? section above)
Make sure you followed all of the steps in the installation and configuration section and pay attention to the verification note at the end of the configuration section
You could use the same principle behind Firebase anonymous authentication but most likely you will run into the same limitations: Identities are associated to app installations (or devices in the best case scenario).
Once a user uninstalls the app, signs out or changes devices the user identity is lost.
If you are already using firebase authentication you want to incorporate react-native-user-identity as another option for the user to sign in (or maybe you just want to replace anonymous authentication).
You should then:
- Create a custom token with the firebase admin sdk.
Custom tokens should be created on the server side of your application, as they should be signed and secured. See Creating custom tokens for firebase.
If you are going serverless you can easily deploy the following cloud function to firebase:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
exports.tokenFromUID = functions.https.onCall( async (data, context) => {
const { uid } = data
try {
return await admin.auth().createCustomToken(uid)
} catch(error) {
console.log('Error creating custom token: ', error)
throw new functions.https.HttpsError('internal', error ? error.message : '')
}
})
With this cloud function you also need to configure permissions for your firebase instance. See this section of the firebase documentation for details
- Sign in with the custom token on your app
Assuming you are using rnfirebase:
Replace this:
import auth from '@react-native-firebase/auth'
const userCredential = await auth().signInAnonymously()
With this:
import auth from '@react-native-firebase/auth'
import functions from '@react-native-firebase/functions'
import RNUserIdentity from 'react-native-user-identity'
// get uid for user...
const uid = await RNUserIdentity.getUserId()
if (uid != null) {
// Server call (a firebase cloud function in this case)
const tokenResponse = await functions().httpsCallable('tokenFromUID')({ uid })
const userCredential = await auth().signInWithCustomToken(tokenResponse.data)
}