🎯 Learning Path:

  1. Complete REL-ID Complete Activation & Login Flow Codelab
  2. You are here → Push Notification Integration

Welcome to the REL-ID Push Notification Integration codelab! This tutorial enhances your existing REL-ID iOS application with secure push notification capabilities using REL-ID SDK's getDeviceToken() callback.

What You'll Build

In this codelab, you'll enhance your existing REL-ID application with:

What You'll Learn

By completing this codelab, you'll master:

  1. REL-ID Push Architecture: Understanding two-channel security model vs standard push notifications
  2. getDeviceToken() Callback: Complete implementation of REL-ID device token callback pattern
  3. Device Token Management: Implementing token retrieval, registration, and refresh cycles
  4. Service Architecture: Building scalable push notification services with singleton patterns
  5. REL-ID SDK Integration: Best practices for integrating with existing REL-ID service layer
  6. Production Deployment: Security best practices, error handling, and monitoring strategies

Prerequisites

Before starting this codelab, ensure you have:

Get the Code from GitHub

The code to get started can be found in a GitHub repository.

You can clone the repository using the following command:

git clone https://github.com/uniken-public/codelab-ios.git

Navigate to the relid-push-notification-token folder in the repository you cloned earlier.

Why REL-ID Push Notifications?

REL-ID push notifications provide a secure, two-channel architecture that goes beyond standard push messaging with secure wake-up signals, MITM-proof channels, transaction approvals, and device-bound credentials.

REL-ID Flow: FCM Wake-up → App Launch → Secure REL-ID Channel → Encrypted Data Retrieval → User Action

Codelab Architecture Overview

This codelab implements two core components:

  1. PushNotificationManager: Singleton service managing device tokens and Firebase integration
  2. RDNADelegateManager: Callback implementation providing tokens to REL-ID SDK via getDeviceToken()

Before implementing push notifications, let's understand how REL-ID's secure notification system works on iOS.

REL-ID Two-Channel Security Model

REL-ID uses a sophisticated two-channel approach for maximum security:

📱 REL-ID Server → FCM/APNS (Wake-up Signal) → iOS App → REL-ID Secure Channel → Encrypted Data

Channel 1: FCM Wake-Up Signal

Channel 2: REL-ID Secure Channel

Device Token Registration Flow

Here's how getDeviceToken() enables secure REL-ID communications:

// iOS Device Registration Flow
APNS Token Generation → FCM Token Generation → getDeviceToken() Callback →
REL-ID Backend Registration → Secure Channel Establishment → Transaction Approval Capability

Step

Description

Security Benefit

1. APNS Token

iOS generates Apple Push Notification Service identifier

Device uniqueness

2. FCM Token

Firebase converts APNS to cross-platform FCM token

Platform abstraction

3. REL-ID Callback

getDeviceToken() provides token to REL-ID SDK

Device-server binding

4. Secure Channel

Establish encrypted communication channel

MITM protection

5. Transaction Support

Enable approve/reject actions with MFA

Multi-factor security

REL-ID Push Notification Use Cases

Once integrated, your app can handle these secure notification types:

Firebase Role: Provides the platform infrastructure (FCM token generation, APNS integration)

REL-ID Role: Provides the secure communication and transaction approval capabilities

Now let's implement the core push notification service that handles FCM token management and iOS integration.

Create PushNotificationManager Singleton

First, create the service that manages all push notification functionality:

// Sources/Uniken/Services/PushNotificationManager.swift

import Foundation
import Firebase
import FirebaseMessaging
import UserNotifications

/// Push Notification Manager
/// Handles FCM token registration for REL-ID SDK
///
/// This manager:
/// 1. Configures Firebase SDK
/// 2. Requests notification permissions
/// 3. Registers for remote notifications (APNS handled by Firebase)
/// 4. Receives FCM token via MessagingDelegate
/// 5. Stores token for SDK's getDeviceToken() callback
/// 6. Handles token refresh automatically
class PushNotificationManager: NSObject {

    // MARK: - Singleton

    static let shared = PushNotificationManager()

    private override init() {
        super.init()
    }

    // MARK: - Properties

    private var apnsToken: String?  // Apple Push Notification Service token (64 chars hex)
    private var fcmToken: String?   // Firebase Cloud Messaging token (140+ chars)
    private var isInitialized = false

    /// Token type for getDeviceToken()
    enum TokenType {
        case apns  // Raw APNS token
        case fcm   // Firebase Cloud Messaging token (recommended)
    }

    // MARK: - Public Methods

    /// Initialize FCM and register for push notifications
    func initialize() {
        if isInitialized {
            print("PushNotificationManager - Already initialized")
            return
        }

        print("PushNotificationManager - Starting FCM initialization for iOS")

        // Configure Firebase (if not already configured)
        if FirebaseApp.app() == nil {
            print("PushNotificationManager - Configuring Firebase")
            FirebaseApp.configure()
        } else {
            print("PushNotificationManager - Firebase already configured")
        }

        // Set messaging delegate
        Messaging.messaging().delegate = self

        // Request notification permissions
        requestPermissions()

        isInitialized = true
        print("PushNotificationManager - Initialization complete")
    }

    /// Get current device token (called by SDK's getDeviceToken callback)
    /// - Parameter type: Token type to return (.fcm recommended for cross-platform)
    /// - Returns: FCM or APNS token string, or empty string if not available
    func getDeviceToken(type: TokenType = .fcm) -> String {
        switch type {
        case .apns:
            if let token = apnsToken {
                print("PushNotificationManager - Returning APNS token, length: \(token.count)")
                return token
            } else {
                print("PushNotificationManager - APNS token not available")
                return ""
            }
        case .fcm:
            if let token = fcmToken {
                print("PushNotificationManager - Returning FCM token, length: \(token.count)")
                return token
            } else {
                print("PushNotificationManager - FCM token not available, falling back to APNS")
                return apnsToken ?? ""
            }
        }
    }

    /// Set APNS token directly from AppDelegate
    /// Called when iOS delivers the APNS token (before Firebase processes it)
    /// - Parameter token: APNS token hex string
    func setAPNSToken(_ token: String) {
        self.apnsToken = token
        print("📱 PushNotificationManager - APNS token stored directly from iOS")
        print("   Length: \(token.count) chars")

        // If FCM token already available, post notification
        if let fcmToken = self.fcmToken {
            NotificationCenter.default.post(
                name: NSNotification.Name("DeviceTokenReady"),
                object: nil,
                userInfo: ["fcm": fcmToken, "apns": token]
            )
        }
    }

    /// Request notification permissions
    func requestPermissions() {
        print("PushNotificationManager - Requesting notification permissions for iOS")

        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            if let error = error {
                print("PushNotificationManager - Permission request error: \(error.localizedDescription)")
                return
            }

            print("PushNotificationManager - Permission granted: \(granted)")

            if granted {
                // Register for remote notifications on main thread
                DispatchQueue.main.async {
                    print("PushNotificationManager - Registering for remote notifications")
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
        }
    }

    /// Cleanup
    func cleanup() {
        print("PushNotificationManager - Cleanup")
        isInitialized = false
        apnsToken = nil
        fcmToken = nil
    }
}

// MARK: - MessagingDelegate

extension PushNotificationManager: MessagingDelegate {

    /// Handle FCM token refresh
    /// Also extracts APNS token from Firebase
    /// - Parameters:
    ///   - messaging: Messaging instance
    ///   - fcmToken: New FCM token
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        guard let fcmToken = fcmToken else {
            print("❌ PushNotificationManager - FCM token is nil")
            return
        }

        // Store FCM token
        self.fcmToken = fcmToken
        print("☁️ PushNotificationManager - FCM token received")
        print("   Length: \(fcmToken.count) chars")
        print("   Token: \(fcmToken)")

        // Log token types for verification
        print("✅ PushNotificationManager - Tokens status:")
        print("   APNS: \(apnsToken != nil ? "Available (from AppDelegate)" : "Not Available")")
        print("   FCM:  Available")

        // Notify that tokens are ready (APNS already captured by AppDelegate)
        NotificationCenter.default.post(
            name: NSNotification.Name("DeviceTokenReady"),
            object: nil,
            userInfo: ["fcm": fcmToken, "apns": apnsToken ?? ""]
        )
    }
}

Service Architecture Benefits

This implementation follows enterprise-grade iOS patterns:

Pattern

Benefit

Implementation

Singleton

Single point of control

static let shared = PushNotificationManager()

Dual-Token Storage

APNS + FCM support

Stores both token types

Delegate Pattern

Firebase integration

MessagingDelegate protocol

Main Thread Dispatch

UI safety

DispatchQueue.main.async

Error Handling

Graceful failure management

Optional unwrapping, logging

Now let's integrate push notification initialization in AppDelegate and capture APNS tokens directly.

Update AppDelegate.swift

Add push notification initialization and APNS token capture:

// AppDelegate.swift

import UIKit
import IQKeyboardManagerSwift
import FirebaseMessaging

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Configure IQKeyboardManager
        IQKeyboardManager.shared.isEnabled = true
        IQKeyboardManager.shared.enableAutoToolbar = true
        IQKeyboardManager.shared.resignOnTouchOutside = true
        IQKeyboardManager.shared.keyboardDistance = 20

        // Initialize push notification manager (FCM + APNS)
        PushNotificationManager.shared.initialize()

        return true
    }

    // MARK: - Push Notifications (APNS Direct Capture)

    /// Captures APNS token directly from iOS (before Firebase receives it)
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("🔔 AppDelegate - didRegisterForRemoteNotificationsWithDeviceToken CALLED!")

        // Convert APNS token to hex string
        let apnsToken = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()

        print("📱 AppDelegate - APNS token received from iOS")
        print("   Length: \(apnsToken.count) chars")
        print("   Token: \(apnsToken)")

        // Store APNS token directly (before Firebase processes it)
        PushNotificationManager.shared.setAPNSToken(apnsToken)

        // Pass to Firebase for FCM generation
        Messaging.messaging().apnsToken = deviceToken
        print("✅ AppDelegate - APNS token passed to Firebase for FCM generation")
    }

    /// Handle APNS registration failure
    func application(_ application: UIApplication,
                     didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("❌ AppDelegate - APNS registration failed: \(error.localizedDescription)")
    }

    // MARK: UISceneSession Lifecycle (existing code)

    func application(_ application: UIApplication,
                     configurationForConnecting connectingSceneSession: UISceneSession,
                     options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration",
                                     sessionRole: connectingSceneSession.role)
    }
}

Implementation Details

iOS Push Token Flow:

  1. App Launch: didFinishLaunchingWithOptions initializes PushNotificationManager
  2. Permission Request: UNUserNotificationCenter requests authorization
  3. APNS Registration: UIApplication.shared.registerForRemoteNotifications()
  4. Token Delivery: iOS delivers APNS token to AppDelegate callback
  5. Hex Conversion: Convert Data to 64-character hex string
  6. Dual Storage: Store in manager + pass to Firebase for FCM generation
  7. FCM Generation: Firebase converts APNS to FCM token
  8. Delegate Callback: MessagingDelegate receives FCM token

Now let's implement the critical getDeviceToken() callback that provides tokens to the REL-ID SDK.

Enhance RDNADelegateManager

Add the device token callback method to your existing delegate manager:

// Sources/Uniken/Services/RDNADelegateManager.swift (addition to existing class)

// MARK: - Device Token Methods (PUSH-NOTIFICATION-SPECIFIC)

/// Get device token for push notifications
/// Called by REL-ID SDK to retrieve the FCM/APNS token
/// - Returns: Device token string (FCM by default, recommended for cross-platform)
func getDeviceToken() -> String {
    // Return FCM token (recommended for cross-platform push notifications)
    // To return APNS token instead, use: .getDeviceToken(type: .apns)
    let token = PushNotificationManager.shared.getDeviceToken(type: .fcm)

    if token.isEmpty {
        print("RDNADelegateManager - SDK requested device token: empty (not yet registered)")
    } else {
        let tokenType = token.count > 100 ? "FCM" : "APNS"
        print("RDNADelegateManager - SDK requested device token")
        print("   Type: \(tokenType)")
        print("   Length: \(token.count) chars")
        print("   Token: \(token)")
    }

    return token
}

Callback Pattern Explanation

How getDeviceToken() Works:

// SDK Callback Flow
REL-ID SDK (Native Code)
    ↓
Calls getDeviceToken() on RDNACallbacks delegate
    ↓
RDNADelegateManager.getDeviceToken()
    ↓
PushNotificationManager.shared.getDeviceToken(type: .fcm)
    ↓
Returns stored FCM token to SDK
    ↓
SDK registers token with REL-ID backend

Token Type Detection:

Understanding iOS Callback Architecture

iOS uses a delegate-based callback pattern:

// Step 1: Set delegate during SDK initialization
let rdna = RDNA.sharedInstance()
rdna.setDelegate(RDNADelegateManager.shared)

// Step 2: SDK calls delegate method when needed
// (happens automatically inside SDK)

// Step 3: Your implementation returns the token
func getDeviceToken() -> String {
    return PushNotificationManager.shared.getDeviceToken(type: .fcm)
}

Memory Management:

Let's configure the iOS project with required dependencies and entitlements.

Update Podfile

Add Firebase Messaging to your Podfile:

# Podfile

platform :ios, '13.0'

target 'YourApp' do
  use_frameworks!

  # Existing dependencies
  pod 'IQKeyboardManagerSwift'
  pod 'SideMenu', '~> 6.5'

  # Firebase for push notifications (FCM)
  pod 'Firebase/Messaging'
end

Then install:

cd ios
pod install

Add GoogleService-Info.plist

  1. Download from Firebase Console:
    • Go to Firebase Console → Project Settings
    • Download GoogleService-Info.plist for your iOS app
    • Critical: Ensure bundle ID matches your Xcode project
  2. Add to Xcode:
    • Drag GoogleService-Info.plist into Xcode project navigator
    • Check "Copy items if needed"
    • Check target membership for your app target

Configure Info.plist

Add background modes for remote notifications:

<!-- Info.plist -->

<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

Configure Entitlements

Add push notifications capability:

<!-- YourApp.entitlements -->

<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.networking.wifi-info</key>
<true/>

Enable Push Notifications in Xcode

  1. Select your project in Xcode
  2. Go to Signing & Capabilities
  3. Click + Capability
  4. Add Push Notifications

Upload APNS Certificate to Firebase

  1. Generate APNS certificate in Apple Developer Portal
  2. Upload to Firebase Console:
    • Go to Project Settings → Cloud Messaging → iOS app
    • Upload .p8 authentication key OR .p12 certificate
  3. Verify APNs Authentication Key shows as configured

Let's thoroughly test your push notification implementation to ensure production readiness.

Test Scenario 1: Complete Token Registration Flow

Prerequisites:

Test Steps:

  1. Build and Run Application
    # Using Xcode
    # Open YourApp.xcworkspace
    # Select target device
    # Press Cmd+R
    
    # Using command line
    xcodebuild -workspace YourApp.xcworkspace \
               -scheme YourApp \
               -sdk iphoneos \
               -destination 'platform=iOS,name=Your Device'
    
  2. Monitor Console Logs Look for this successful initialization sequence:
    ✅ PushNotificationManager - Starting FCM initialization for iOS
    ✅ PushNotificationManager - Permission granted: true
    ✅ PushNotificationManager - Registering for remote notifications
    ✅ AppDelegate - APNS token received from iOS
    ✅ PushNotificationManager - FCM token received
    ✅ RDNADelegateManager - SDK requested device token (Type: FCM)
    
  3. Verify Token Generation Confirm you see:
    • APNS token (64 characters)
    • FCM token (140+ characters)
  4. Test Token Refresh The refresh listener fires automatically when Firebase updates the token

Expected Results:

Test Scenario 2: Permission Handling

iOS Permission Test:

  1. First launch - verify notification permission dialog appears
  2. Allow - verify token registration continues
  3. Deny - verify graceful handling (no crashes)
  4. Settings Test - disable notifications in Settings, verify app handles gracefully

Expected Permission Flow:

📱 UNUserNotificationCenter authorization request → User allows → APNS registration
📱 UNUserNotificationCenter authorization request → User denies → Graceful fallback

Test Scenario 3: Firebase Configuration Validation

Test Missing GoogleService-Info.plist:

  1. Temporarily rename GoogleService-Info.plist to GoogleService-Info.plist.backup
  2. Build project in Xcode
  3. Verify error handling - should see Firebase initialization failure logs
  4. Restore file and rebuild successfully

Test APNS Certificate:

# Verify APNS certificate is valid
# Check expiration in Apple Developer Portal
# Verify upload to Firebase Console

Production Readiness Checklist

Before deploying to production, verify:

Congratulations! You've successfully implemented secure push notification integration with the REL-ID SDK on iOS. Here's your complete implementation overview.

🚀 What You've Built

Secure Device Registration - APNS/FCM tokens registered with REL-ID backend for two-channel security ✅ Firebase Integration - Complete FCM setup with Firebase Messaging delegate ✅ Production-Ready Service - Singleton architecture with error handling and token refresh ✅ AppDelegate Integration - APNS token capture and Firebase integration ✅ Callback Implementation - getDeviceToken() callback providing tokens to SDK

Key Files Created/Modified

Sources/Uniken/Services/
├── PushNotificationManager.swift     ✅ FCM/APNS token management singleton
└── RDNADelegateManager.swift         ✅ Enhanced with getDeviceToken() callback

AppDelegate.swift                     ✅ APNS token capture and initialization

Configuration:
├── Podfile                           ✅ Firebase/Messaging dependency
├── GoogleService-Info.plist          ✅ Firebase configuration
├── Info.plist                        ✅ UIBackgroundModes: remote-notification
└── YourApp.entitlements              ✅ aps-environment capability

Architecture Achievement

Your implementation demonstrates enterprise-grade iOS patterns:

Component

Pattern

Benefit

PushNotificationManager

Singleton

Centralized token management

AppDelegate Integration

Lifecycle Hook

Early initialization

RDNADelegateManager

Callback Pattern

SDK token provision

Firebase Configuration

CocoaPods

Professional dependency management

Error Handling

Optional Unwrapping

Production reliability

Security Benefits Unlocked

Your REL-ID push notification integration now enables:

iOS-Specific Patterns You've Mastered

Additional Resources

🎉 Congratulations!

You've successfully implemented secure push notification capabilities that integrate seamlessly with the REL-ID security ecosystem on iOS. Your app can now participate in secure, two-channel communications for transaction approvals, authentication challenges, and security notifications.

Your users now have a more secure, responsive authentication experience with the power of REL-ID's push notification infrastructure!