Identity Verification

Authenticate External User IDs, SMS, and emails sent to OneSignal.

Overview

OneSignal offers an enhanced security feature called Identity Verification to help prevent user impersonation. This feature utilizes JSON Web Tokens – or JWTs, securely generated on your server. To verify subscription information, these tokens are passed to your app and OneSignal’s API.

We recommend enabling Identity Verification to:

  • Log in users
  • Add email subscriptions
  • Add SMS subscriptions
  • Modify user identities

Prerequisites

🚧

Web and Cross-Platform Support Coming Soon!

Support for popular cross-platform frameworks like React Native, Flutter, and Unity is on the way, along with web and UI frameworks such as React, Vue, and Angular.

Setup

1. Generate new keys

Log in to your OneSignal account and navigate to Settings > Keys & IDs > Identity Verification.

Identity Verification configuration

Identity Verification configuration

Click Generate New Keys to create a new key pair.

Creating new key pair

Creating new key pair

Download the PEM file or copy the private key, making sure to store the private key securely.

Identity Verification key pair

Identity Verification key pair

❗️

Don't leak keys

Always store your private keys in a secure environment, such as a key management system. Never expose private keys in

2. Generate verification JWT on your backend

Identity verification requires authenticating the end-user with your authentication server before logging them into OneSignal. When the end-user authenticates with your backend, generate the token and include it in the auth response to the device. If your app doesn’t run a backend server, consider setting up a lightweight server to verify users and generate these tokens.

JWT payload

The JWT can have the following properties:

PropertyRequiredDescription
issYour OneSignal App ID.
expThe token expiration date.
identityThe user's alias.
subscriptionsOptional; required only when adding Email and SMS subscriptions to a user.

Signing the JWT

Sign the JWT using the ES256 algorithm. Ensure your backend is configured to use this signing method to avoid verification issues when sending the JWT to OneSignal. We recommend a JWT Library to do this.

Example using jsonwebtoken:

import jwt from 'jsonwebtoken';

const APP_ID = process.env['ONESIGNAL_APP_ID']
const IDENTITY_VERIFICATION_SECRET = process.env['ONESIGNAL_IDENTITY_VERIFICATION_SECRET_KEY']

// Generates JWT, potentially with subscription claims, for the user identified by the External ID
function signOneSignalJWT(externalId, subscriptions) {
  return jwt.sign({
    iss: APP_ID,
    exp: Math.floor(Date.now() / 1000) + 3600, // 1-hour expiration
    identity: {
      'external_id': externalId,
    },
    subscriptions
  },
  IDENTITY_VERIFICATION_SECRET,
  { algorithm: 'ES256' });
}

// Pass this token to your mobile app to use with the `login` SDK method
const onesignalJWT = signOneSignalJWT('EXTERNAL_ID');

The private key is in the previous step's file we downloaded from the Dashboard.

Including subscriptions

Ideally, subscription details, such as email or phone number, get included in the JWT payload when logging a user in. If these details aren’t available upfront, your verification server must provide an endpoint to generate tokens dynamically as subscription information becomes available.

Example: Generating JWT to add subscriptions

const subscriptions = [
  {
      "type": "Email",
      "token": "[email protected]"
  },
  {
      "type": "SMS",
      "token": "+12345678"
  }
]
const onesignalJWT = signOneSignalJWT('APP_ID', 'EXTERNAL_ID', subscriptions)

3. Pass JWT to the login method

Once your backend generates the JWT, call the login method with it. This token ensures the user’s identity is verified before any changes, such as adding an email or SMS subscription, can be made.

Example of logging in:

// The External ID comes from your backend
val externalId = "123456789" 

// The JWT is generated on your backend using the authenticated user's
// External ID or any other custom aliases you may have set.
val onesignalJWT = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3OGUzMjA4Mi1lYTI3LTQyZTMtYTg5OC1jNzJlMTQxODI0ZWEiLCJleHAiOjE2NTc3NzU4NzIsImlkZW50aXR5Ijp7ImFsaWFzX2xhYmVsX2V4YW1wbGUiOiJhbGlhc19pZF9leGFtcGxlIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoidGVzdEBkb21haW4uY29tIn0seyJ0eXBlIjoiU01TIiwidG9rZW4iOiIrMTIzNDU2NzgifV19._iazvUy6krturWicV1eXqPzptzIB8y_GmvDXfFp6OXNxiLvI2wRkWS5iH_gJWPmyZc48KLpJXgR2RT6Jn8_5Aw"

OneSignal.login(externalId, onesignalJWT)

// The External ID comes from your backend
let externalId = "123456789"

// The JWT is generated on your backend using the authenticated user's
// External ID or any other custom aliases you may have set.
let onesignalJWT = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3OGUzMjA4Mi1lYTI3LTQyZTMtYTg5OC1jNzJlMTQxODI0ZWEiLCJleHAiOjE2NTc3NzU4NzIsImlkZW50aXR5Ijp7ImFsaWFzX2xhYmVsX2V4YW1wbGUiOiJhbGlhc19pZF9leGFtcGxlIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoidGVzdEBkb21haW4uY29tIn0seyJ0eXBlIjoiU01TIiwidG9rZW4iOiIrMTIzNDU2NzgifV19._iazvUy6krturWicV1eXqPzptzIB8y_GmvDXfFp6OXNxiLvI2wRkWS5iH_gJWPmyZc48KLpJXgR2RT6Jn8_5Aw"

OneSignal.login(externalId: externalId, token: onesignalJWT)

// The External ID comes from your backend
String externalId = "123456789";

// The JWT is generated on your backend using the authenticated user's
// External ID or any other custom aliases you may have set.
String onesignalJWT = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3OGUzMjA4Mi1lYTI3LTQyZTMtYTg5OC1jNzJlMTQxODI0ZWEiLCJleHAiOjE2NTc3NzU4NzIsImlkZW50aXR5Ijp7ImFsaWFzX2xhYmVsX2V4YW1wbGUiOiJhbGlhc19pZF9leGFtcGxlIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoidGVzdEBkb21haW4uY29tIn0seyJ0eXBlIjoiU01TIiwidG9rZW4iOiIrMTIzNDU2NzgifV19._iazvUy6krturWicV1eXqPzptzIB8y_GmvDXfFp6OXNxiLvI2wRkWS5iH_gJWPmyZc48KLpJXgR2RT6Jn8_5Aw"

OneSignal.login(externalId, onesignalJWT);

// The External ID comes from your backend
NSString* externalId = @"123456789";
  
// The JWT is generated on your backend using the authenticated user's
// External ID or any other custom aliases you may have set.
NSString* onesignalJWT = @"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3OGUzMjA4Mi1lYTI3LTQyZTMtYTg5OC1jNzJlMTQxODI0ZWEiLCJleHAiOjE2NTc3NzU4NzIsImlkZW50aXR5Ijp7ImFsaWFzX2xhYmVsX2V4YW1wbGUiOiJhbGlhc19pZF9leGFtcGxlIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoidGVzdEBkb21haW4uY29tIn0seyJ0eXBlIjoiU01TIiwidG9rZW4iOiIrMTIzNDU2NzgifV19._iazvUy6krturWicV1eXqPzptzIB8y_GmvDXfFp6OXNxiLvI2wRkWS5iH_gJWPmyZc48KLpJXgR2RT6Jn8_5Aw";

[OneSignal login:externalId withToken:onesignalJWT];
  

4. Handle JWT lifecycle events

You’ll need to implement a dedicated endpoint on your backend to handle scenarios like token invalidation. This endpoint should provide a refreshed JWT when OneSignal requests an update.

Example of handling token invalidation and refreshing the JWT:

OneSignal.addUserJwtInvalidatedListner(
   object : IUserJwtInvalidatedListener {
       override fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent) {
         val externalId = event.externalId
         val onesignalJWT = "" 
         updateUserJwt(externalId, onesignalJWT)
       }
   },
)

class AppDelegate: UIResponder, UIApplicationDelegate, OSUserJwtInvalidatedListener {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
    // Set self to listen for JWT invalidated events
    OneSignal.addUserJwtInvalidatedListener(self)
    
    // Remove the JWT listener as needed by calling:
    // `OneSignal.removeUserJwtInvalidatedListener(self)`
  }
  
  // Required to conform to `OSUserJwtInvalidatedListener` protocol
  func onUserJwtInvalidated(event: OneSignalUser.OSUserJwtInvalidatedEvent) {
    // Get the expired user's External ID
    let externalId = event.externalId
    
    // Fetch a new JWT from your backend for the user
    let onesignalJWT = "yourUpdatedToken"
    
    // Provide the new JWT to the SDK
    OneSignal.updateUserJwt(externalId: externalId, token: onesignalJWT)
  }
}

OneSignal.addUserJwtInvalidatedListener(event -> {
  // Get the expired user's External ID
  String externalId = event.getExternalId();  
  
  // Fetch a new JWT from your backend for the user
  String onesignalJWT = "yourUpdatedToken";
  
  // Provide the new JWT to the SDK
  OneSignal.updateUserJwt(externalId, onesignalJWT);
});

@interface MyListener: NSObject<OSUserJwtInvalidatedListener>
@end

@implementation MyListener
- (void)onUserJwtInvalidatedWithEvent:(OSUserJwtInvalidatedEvent * _Nonnull)event { 
  // Get the expired user's External ID
  NSString *externalId = event.externalId;
    
  // Fetch a new JWT from your backend for the user
  NSString *onesignalJWT = @"yourUpdatedToken";

  // Provide the new JWT to the SDK
	[OneSignal updateUserJwt:externalId withToken:onesignalJWT];
}

// Add or remove your User Jwt Invalidated Listener
[OneSignal addUserJwtInvalidatedListener:myListener];
[OneSignal removeUserJwtInvalidatedListener:myListener];

This ensures that when a user’s JWT is invalidated, a new one can be fetched from your backend and passed to OneSignal. You can also use this function to generate a token with an email and phone number, allowing you to manage email and SMS subscriptions if the token created during authentication does not contain them.

5. Enable Token Identity Verification in the Dashboard

From Settings > Keys & IDs, toggle Token Identity Verification to enable.

Enabling Token Identity Verifcation

Enabling Token Identity Verification

Once enabled, your app must send OneSignal JWTs to verify subscription authenticity. Additionally, your app is required to call the login method using a JWT generated by your identity verification token server.

Adding subscriptions

You don’t need to take extra steps to add subscriptions from your mobile app; calling the login method automatically handles this for you.

Add an email

OneSignal.getUser().addEmail(emailAddress);

OneSignal.getUser().addEmail(emailAddress)

// If you have not already included it in your JWT token, update the JWT with the email
let onesignalJWT = "newTokenThatContainsTheEmailToAdd"
OneSignal.updateUserJwt(externalId: externalId, token: onesignalJWT)

// Add the email
OneSignal.User.addEmail(emailAddress)

// If you have not already included it in your JWT token, update the JWT with the email
NSString *onesignalJWT = @"newTokenThatContainsTheEmailToAdd";
[OneSignal updateUserJwt:externalId withToken:onesignalJWT];

// Add the email
[OneSignal.User addEmail:emailAddress];

Add a phone number

OneSignal.getUser().addSms(smsNumber);

OneSignal.getUser().addSms(smsNumber)

// If you have not already included it in your JWT token, update the JWT with the sms
let onesignalJWT = "newTokenThatContainsTheSmsToAdd"
OneSignal.updateUserJwt(externalId: externalId, token: onesignalJWT)

// Add the sms number
OneSignal.User.addSms(smsNumber)

// If you have not already included it in your JWT token, update the JWT with the sms
NSString *onesignalJWT = @"newTokenThatContainsTheSmsToAdd";
[OneSignal updateUserJwt:externalId withToken:onesignalJWT];

// Add the sms number
[OneSignal.User addSms:smsNumber];

REST API

When Token Identity Verification is enabled, all requests to the following APIs must include a server-generated JWT in the headers as a bearer token, e.g., Authorization: Bearer <JWT>.