🎯 Learning Path:
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.
In this codelab, you'll enhance your existing REL-ID application with:
getDeviceToken() callbackBy completing this codelab, you'll master:
Before starting this codelab, ensure you have:
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.
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
This codelab implements two core components:
getDeviceToken()Before implementing push notifications, let's understand how REL-ID's secure notification system works on iOS.
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
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 |
| Device-server binding |
4. Secure Channel | Establish encrypted communication channel | MITM protection |
5. Transaction Support | Enable approve/reject actions with MFA | Multi-factor security |
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.
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 ?? ""]
)
}
}
This implementation follows enterprise-grade iOS patterns:
Pattern | Benefit | Implementation |
Singleton | Single point of control |
|
Dual-Token Storage | APNS + FCM support | Stores both token types |
Delegate Pattern | Firebase integration |
|
Main Thread Dispatch | UI safety |
|
Error Handling | Graceful failure management | Optional unwrapping, logging |
Now let's integrate push notification initialization in AppDelegate and capture APNS tokens directly.
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)
}
}
iOS Push Token Flow:
didFinishLaunchingWithOptions initializes PushNotificationManagerNow let's implement the critical getDeviceToken() callback that provides tokens to the REL-ID SDK.
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
}
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:
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.
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
GoogleService-Info.plist for your iOS appGoogleService-Info.plist into Xcode project navigatorAdd background modes for remote notifications:
<!-- Info.plist -->
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
Add push notifications capability:
<!-- YourApp.entitlements -->
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
.p8 authentication key OR .p12 certificateLet's thoroughly test your push notification implementation to ensure production readiness.
Prerequisites:
GoogleService-Info.plistTest Steps:
# 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'
✅ 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)
Expected Results:
iOS Permission Test:
Expected Permission Flow:
📱 UNUserNotificationCenter authorization request → User allows → APNS registration
📱 UNUserNotificationCenter authorization request → User denies → Graceful fallback
Test Missing GoogleService-Info.plist:
GoogleService-Info.plist to GoogleService-Info.plist.backupTest APNS Certificate:
# Verify APNS certificate is valid
# Check expiration in Apple Developer Portal
# Verify upload to Firebase Console
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.
✅ 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
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
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 |
Your REL-ID push notification integration now enables:
🎉 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!