Push Authentication

Push authentication lets a user approve a sign-in request using their mobile device, assuming they are already signed in to an app that is linked to the same Descope project. For example, the user might already be signed in to an app on their phone, and they're trying to sign in on their laptop browser to the website.

Using push authentication, Descope sends a push notification to their mobile device. The user taps Approve in the notification or in an in-app UI, and the sign-in completes.

The mobile app has two responsibilities:

  1. Enroll the device by registering its push token with Descope while the user has an active session.
  2. Handle the incoming push notification and report the user's decision back to Descope.

Set Up Push Notifications

Push authentication leverages the standard platform push services, so the app must first be able to receive normal push notifications. This is standard iOS/Android setup and is documented by Apple and Google.

  • iOS (APNs) — Enable the Push Notifications capability, request notification permission, and register for remote notifications to obtain a device token. See Apple's Registering your app with APNs.
  • Android (FCM) — Add Firebase to your app, include the Firebase Messaging SDK, and implement a FirebaseMessagingService to receive messages and the FCM registration token. See Google's Set up a Firebase Cloud Messaging client app on Android.

Once your app can receive a push token and display notifications, continue below.

Enable Push Authentication

Configure the Apple Push Notification (APN) and Firebase Cloud Messaging (FCM) connectors that deliver the notifications in the Descope Console. Then, in the Push Authentication settings, select these configured connectors for each platform: iOS and Android.

Handle the Notification Payload

Descope sends the notification with a transactionId that identifies the pending sign-in. Your app reads this value and passes it to the SDK when the user responds. The payload also includes userId and projectId for reference.

The APNs payload carries the transaction ID alongside a standard aps alert. The category is descope_sign_in, which you can use to attach custom notification actions.

{
  "aps": {
    "alert": "You have a new sign-in request",
    "sound": "default",
    "category": "descope_sign_in",
    "content-available": 1
  },
  "transactionId": "<transaction-id>",
  "userId": "<user-id>",
  "projectId": "<project-id>"
}

Read the values from the notification's userInfo when it arrives.

func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    guard let transactionId = userInfo["transactionId"] as? String else {
        completionHandler(.noData)
        return
    }
    // Show a notification with Approve / Deny actions that carry the transactionId,
    // then call Descope.push.finish() when the user responds.
    showPushAuthNotification(transactionId: transactionId)
    completionHandler(.newData)
}

Descope sends a data message so your FirebaseMessagingService is invoked whether the app is in the foreground or background. Read the values from message.data.

{
  "data": {
    "title": "Sign-in Request",
    "body": "You have a new sign-in request",
    "transactionId": "<transaction-id>",
    "userId": "<user-id>",
    "projectId": "<project-id>"
  }
}
override fun onMessageReceived(message: RemoteMessage) {
    val transactionId = message.data["transactionId"] ?: return
    val title = message.data["title"] ?: "Sign-in Request"
    val body = message.data["body"] ?: ""
    // Show a notification with Approve / Deny actions that carry the transactionId,
    // then call Descope.push.finish() when the user responds.
    showPushAuthNotification(transactionId, title, body)
}

Using the SDK

Both SDK functions require the refreshJwt from an active DescopeSession, so the user must already be signed in on the device.

Enroll the Device

Register the device's push token after the user signs in and grants notification permission. Call enroll again whenever the platform issues a new token.

On iOS the device token arrives as Data in didRegisterForRemoteNotificationsWithDeviceToken. Convert it to a hex string before enrolling. Set development to true for debug builds that use the APNs sandbox, and false for production/TestFlight builds.

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02x", $0) }.joined()
    guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
    Task {
        do {
            try await Descope.push.enroll(token: token, development: false, refreshJwt: refreshJwt)
        } catch {
            print("Failed to enroll device: \(error)")
        }
    }
}

On Android the token comes from FCM (onNewToken or FirebaseMessaging.getInstance().token).

val refreshJwt = Descope.sessionManager.session?.refreshJwt ?: return
try {
    Descope.push.enroll(token = fcmToken, refreshJwt = refreshJwt)
} catch (e: DescopeException) {
    // Handle enrollment errors
}

Approve or Deny a Request

When the user responds to the notification, call finish with the transactionId from the payload and whether the request was approved.

func completePushAuth(transactionId: String, approved: Bool) async {
    guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
    do {
        try await Descope.push.finish(transactionId: transactionId, approved: approved, refreshJwt: refreshJwt)
    } catch {
        print("Failed to complete push auth: \(error)")
    }
}
suspend fun completePushAuth(transactionId: String, approved: Boolean) {
    val refreshJwt = Descope.sessionManager.session?.refreshJwt ?: return
    try {
        Descope.push.finish(transactionId, approved, refreshJwt)
    } catch (e: DescopeException) {
        // Handle errors
    }
}

After finish succeeds, the pending sign-in on the other device completes with the matching result.

Was this helpful?

On this page