Authorization for React Native SDK

Full authorization reference for the React Native SDK

The SDK communicates with Bloomreach APIs over authorized HTTPS—either the Engagement API directly (ProjectConfig) or the Data hub event stream API (StreamConfig). Authorization modes vary depending on the integration type:

  • Token authorization (default for ProjectConfig): public API access with an API key
  • Customer token authorization (optional for ProjectConfig): private API access with JWT through a native authorization provider
  • SDK auth token authorization (for StreamConfig): JWT-based API access with automatic token lifecycle management

Choose the authorization mode that matches your security requirements.

Token authorization

The default token authorization mode provides public API access using an API key as a token.

Token authorization is used for the following API endpoints by default:

  • POST /track/v2/projects/<projectToken>/customers for tracking of customer data
  • POST /track/v2/projects/<projectToken>/customers/events for tracking of event data
  • POST /track/v2/projects/<projectToken>/campaigns/clicks for tracking campaign events
  • POST /data/v2/projects/<projectToken>/customers/attributes for fetching recommendations
  • POST /data/v2/projects/<projectToken>/consent/categories for fetching consents
  • POST /webxp/s/<projectToken>/inappmessages?compatibility=3 for fetching in-app messages
  • POST /webxp/projects/<projectToken>/appinbox/fetch for fetching of App Inbox data
  • POST /webxp/projects/<projectToken>/appinbox/markasread for marking of App Inbox message as read
  • POST /campaigns/send-self-check-notification?project_id=<projectToken> for part of self-check push notification flow

Developers must set the token using the authorizationToken configuration parameter when initializing the SDK:

import Exponea from 'react-native-exponea-sdk'

Exponea.configure({
  authorizationToken: "Token YOUR_API_KEY",
  ...
}).catch(error => console.log(error))
❗️

Now you need to specify your applicationId. Refer to the Configure application ID for further information.

Customer token authorization

❗️

Customer token authorization (advancedAuthEnabled) works only with ProjectConfig integration. If you set advancedAuthEnabled to true with a StreamConfig integration, the SDK logs a warning and ignores the setting. For stream-based integrations, use SDK auth token authorization instead.

Customer token authorization is optional and provides private API access to select Engagement API endpoints. The customer token contains encoded customer IDs and a signature. When the Bloomreach Engagement API receives a customer token, it first verifies the signature and only processes the request if the signature is valid.

The customer token is encoded using JSON Web Token (JWT), an open industry standard RFC 7519 that defines a compact and self-contained way for securely transmitting information between parties.

The SDK sends the customer token in Bearer <value> format. Currently, the SDK supports customer token authorization for the following Engagement API endpoints:

  • POST /webxp/projects/<projectToken>/appinbox/fetch for fetching of AppInbox data
  • POST /webxp/projects/<projectToken>/appinbox/markasread for marking of AppInbox message as read

Developers can enable customer token authorization by setting the advancedAuthEnabled configuration parameter to true when initializing the SDK:

import Exponea from 'react-native-exponea-sdk'

Exponea.configure({
  advancedAuthEnabled: true,
  ...
}).catch(error => console.log(error))
❗️

Now you need to specify your applicationId. Refer to the Configure application ID for further information.

Additionally, developers must implement an authorization provider that provides a valid JWT token that encodes the relevant customer ID(s) and private API key ID. You must implement a different provider in native code for each platform.

❗️

Customer tokens must be generated by a party that can securely verify the customer's identity. Usually, this means that customer tokens should be generated during the application backend login procedure. When the customer identity is verified (using password, 3rd party authentication, Single Sign-On, etc.), the application backend should generate the customer token and send it to the device running the SDK.

📘

Refer to Generating customer token in the customer token documentation for step-by-step instructions to generate a JWT customer token.

Android authorization provider

First, implement the com.exponea.sdk.services.AuthorizationProvider interface as in the following example:

import com.exponea.sdk.services.AuthorizationProvider;

public class ExampleAuthProvider implements AuthorizationProvider {
    @Nullable
    @Override
    public String getAuthorizationToken() {
        return "eyJ0eXAiOiJKV1Q...";
    }
}

Then register your authorization provider in the AndroidManifest.xml file as in the following example:

<application
    ...
    <meta-data
        android:name="ExponeaAuthProvider"
        android:value="com.your.app.security.ExampleAuthProvider"
        />
</application>

Troubleshooting

If your authorization provider is not working correctly, SDK initialization will fail. Check the log for details:

  1. If you enable customer token authorization using the configuration flag advancedAuthEnabled but the SDK can't find an AuthorizationProvider implementation, you'll see the following message logged:
    Advanced auth has been enabled but provider has not been found
  2. If you register your class in AndroidManifest.xml but the SDK can't find that class, you'll see the following message logged:
    Registered <your class> class has not been found` with detailed info.
  3. If you register your class in AndroidManifest.xml but the class doesn't implement the AuthorizationProvider interface, you'll see the following message logged:
    Registered <your class> class has to implement com.exponea.sdk.services.AuthorizationProvider

The AuthorizationProvider is loaded during SDK initialization or after calling ExponeaPlugin().anonymize(). You should see the above log messages at the same time.

iOS authorization provider

Implement the AuthorizationProviderType protocol with the @objc attribute as in the following example:

@objc(ExponeaAuthProvider)
public class ExampleAuthProvider: NSObject, AuthorizationProviderType {
    required public override init() { }
    public func getAuthorizationToken() -> String? {
        "YOUR JWT TOKEN"
    }
}

Troubleshooting

If you define ExponeaAuthProvider but it is not working as expected, check the logs for the following:

  1. If you enable customer token authorization by setting the configuration flag advancedAuthEnabled to true but the SDK can't find a provider implementation, it will log the following message:
    Advanced authorization flag has been enabled without provider
  2. The registered class musty extend NSObject. If it doesn't, you'll see the following log message:
    Class ExponeaAuthProvider does not conform to NSObject
  3. The registered class must conform to AuthorizationProviderType. If it doesn't, you'll see the following log message:
    Class ExponeaAuthProvider does not conform to AuthorizationProviderType

Asynchronous authorization provider implementation

The customer token value is requested for every HTTP call at runtime. The method getAuthorizationToken() is written for synchronous usage but is invoked in a background thread. Therefore, you are able to block any asynchronous token retrieval (i.e. other HTTP call) and wait for the result by blocking this thread. If the token retrieval fails, you may return a NULL value but the request will automatically fail.

Example for Android:

class ExampleAuthProvider : AuthorizationProvider {
    override fun getAuthorizationToken(): String? = runBlocking {
        return@runBlocking suspendCoroutine { done ->
            retrieveTokenAsync(
                    success = {token -> done.resume(token)},
                    error = {error -> done.resume(null)}
            )
        }
    }
}

Example for iOS:

@objc(ExponeaAuthProvider)
public class ExampleAuthProvider: NSObject, AuthorizationProviderType {
    required public override init() { }
    public func getAuthorizationToken() -> String? {
        let semaphore = DispatchSemaphore(value: 0)
        var token: String?
        let task = yourAuthTokenReqUrl.dataTask(with: request) {
            token = $0
            semaphore.signal()
        }
        task.resume()
        semaphore.wait()
        return token
    }
}
👍

Different network libraries support different approaches but the principle stays same - feel free to block the invocation of the getAuthorizationToken method.

Customer token retrieval policy

The customer token value is requested for every HTTP call that requires it.

Typically, JWT tokens have their own expiration lifetime and can be used multiple times. The SDK does not store the token in any cache. Developers may implement their own token cache as they see fit. For example:

Example for Android:

class ExampleAuthProvider : AuthorizationProvider {

    private var tokenCache: String? = null

    override fun getAuthorizationToken(): String? = runBlocking {
         if (tokenCache.isNullOrEmpty()) {
             tokenCache = suspendCoroutine { done ->
                 retrieveTokenAsync(
                     success = {token -> done.resume(token)},
                     error = {error -> done.resume(null)}
                 )
             }
         }
         return@runBlocking tokenCache
     }
}

Example for iOS:

@objc(ExponeaAuthProvider)
public class ExampleAuthProvider: NSObject, AuthorizationProviderType {
    required public override init() { }

    private var tokenCache: String?
    private var lifetime: Double?

    public func getAuthorizationToken() -> String? {
        if tokenCache == nil || hasExpired(lifetime) {
            (tokenCache, lifetime) = loadJwtToken()
        }
        return tokenCache
    }

    private func loadJwtToken() -> String? {
        ...
    }
}
❗️

Please consider to store your cached token more securely. Android offers multiple options such as KeyStore or Encrypted Shared Preferences.

❗️

A customer token is valid until its expiration and is assigned to the current customer IDs. Bear in mind that if customer IDs change (due to invoking the identifyCustomer or anonymize methods), the customer token may become invalid for future HTTP requests invoked for new customer IDs.

Stream integration

SDK auth token authorization

If you're using a StreamConfig integration, the SDK uses JWT-based authentication through an SDK auth token for all authorized API calls.

Set the SDK auth token

You can set the SDK auth token in three ways:

  1. During SDK initialization, by including it in the CustomerIdentity passed as the optional second argument to Exponea.configure(). For more details, see Initialize with customer identity.

  2. During customer identification, by including it in the CustomerIdentity passed to Exponea.identifyCustomer():

import Exponea from 'react-native-exponea-sdk';

Exponea.identifyCustomer(
  {
    customerIds: { registered: '[email protected]' },
    sdkAuthToken: 'your-jwt-token',
  }
).catch((error) => console.log(error));
  1. Independently, using the setSdkAuthToken() method:
Exponea.setSdkAuthToken('your-jwt-token').catch((error) => console.log(error));
❗️

setSdkAuthToken works only with a StreamConfig integration. If you call it with ProjectConfig, the SDK logs a warning and ignores the call.

Token storage

The SDK stores the auth token persistently across app restarts. It clears the token automatically when anonymize() is called or when identifyCustomer() is called without a token.

SdkAuthErrorCallback

Register an SdkAuthErrorCallback using setSdkAuthErrorCallback() to be notified of authentication failures and to provide fresh tokens:

import Exponea from 'react-native-exponea-sdk';
import type { SdkAuthError } from 'react-native-exponea-sdk';

Exponea.setSdkAuthErrorCallback((error: SdkAuthError) => {
  const newToken = fetchNewTokenFromYourBackend();
  Exponea.setSdkAuthToken(newToken);
});

Use removeSdkAuthErrorCallback() to deregister:

Exponea.removeSdkAuthErrorCallback();

The SdkAuthError object contains:

NameTypeDescription
errorCodeSdkAuthErrorCodeOne of TOKEN_ABOUT_TO_EXPIRE, TOKEN_EXPIRED, TOKEN_REJECTED, TOKEN_NOT_PROVIDED. iOS only: TOKEN_INSUFFICIENT.
customerIdsRecord<string, string>The current customer IDs.

The SdkAuthErrorCode enum includes the following values:

ValueDescription
TOKEN_ABOUT_TO_EXPIREThe current token is approaching its expiration time. The SDK proactively requests a new token.
TOKEN_EXPIREDThe current token has expired. The SDK requests a new token.
TOKEN_REJECTEDThe server rejected the token. The SDK requests a new token.
TOKEN_NOT_PROVIDEDNo token is set. The SDK cannot make authorized requests.
TOKEN_INSUFFICIENT(iOS only) Reserved for future use.
❗️

The SDK invokes the callback on a background thread. You can perform asynchronous work such as fetching a new token, but the SDK re-reads the token from its repository after the callback returns. If you need to block on an async operation (for example, an HTTP call to your backend), complete it within the callback before it returns.

❗️

The SDK assigns the auth token to the current customer IDs. If customer IDs change when you call identifyCustomer or anonymize, the SDK clears the token. Provide a new token for the new customer.

Configure application ID

Multiple mobile apps: If your Engagement project supports multiple mobile apps, specify the applicationId in your configuration. This helps distinguish between different apps in your project.

Exponea.configure({
  ...,
  applicationId: '<Your application id>',
  ...
}).catch(error => console.log(error))

Make sure your applicationId value matches exactly Application ID configured in your Bloomreach Engagement under Project Settings > Campaigns > Channels > Push Notifications.

Single mobile app: If your Engagement project supports only one app, you can skip the applicationId configuration. The SDK will automatically use the default value "default-application".

© Bloomreach, Inc. All rights reserved.