Offline functions
The SDK offers the possibility to use specific functionality without an Internet connection or an active connection to the SSMS: generation of one-time passwords (OTPs).
To enable these offline functions, you need to:
- Define
ssms.allowOfflinePinVerificationin themc_config.jsonfile - Have a user that is already activated and logged in at least once
{
"useScp": false,
"useTokenBasedLogin": false,
"useSmartScreen": false,
"astServerBackend": "ssms",
"ssms": {
"allowOfflinePinVerification": true
}
}
Note: OTP verification can only be successful if app digest check is enabled on SSMS.
allowOfflinePinVerification:
- true: The user's PIN will be cached. Once the user's PIN is provided,
GenerateOtpEventcan be triggered without a PIN. - false: The PIN will never be cached and each time a
GenerateOtpEventis triggered, the PIN needs to be provided.
GenerateOtpEvent has 3 different constructors:
GenerateOtpEvent(byte[] optionalData, String pin, UserIdentifier userIdentifier): Can be used always.GenerateOtpEvent(byte[] optionalData, String pin): Can be used if the user is logged in online, or if aGenerateOtpEventwith auserIdentifierhas been triggered previously.GenerateOtpEvent(byte[] optionalData): Can be used ifallowOfflinePinVerificationis set totrueinmc_config.jsonand aGenerateOtpEventwas previously triggered with auserIdentifierand PIN, or the user is logged in online.
ATC Synchronization
Important: OTP generation relies on Application Transaction Counter (ATC) synchronization between client and server. When these counters become too out of sync, OTP verification will fail due to ATC variance limits being exceeded.
Current limitations:
- ATC synchronization does not run automatically in the MC-SDK
- Must be manually triggered using
EnableAtcResynchronizationDuringNextLoginEvent - Recommended approach: Trigger ATC resynchronization before every login to prevent synchronization issues
- When ATC limits are exceeded, OTP and Secure Sequence creation will be blocked and return a status indicating that login is required
iOS/Swift
Swift Generate OTP
func performGenerateOtp(otpRequest: GenerateOtpRequest, completion: @escaping (ActionResult, GenerateOtpResult?) -> Void) {
let event: KSMGenerateOtpEvent
if !otpRequest.pin.isEmpty && !otpRequest.user.isEmpty {
let userIdentifier = KsUserIdentifier(tenantId: otpRequest.tenant, userId: otpRequest.user)
event = KSMGenerateOtpEvent(optionalData: otpRequest.data, userIdentifier: userIdentifier, pin: otpRequest.pin)
} else if !otpRequest.pin.isEmpty {
event = KSMGenerateOtpEvent(optionalData: otpRequest.data, pin: otpRequest.pin)
} else {
event = KSMGenerateOtpEvent(optionalData: otpRequest.data)
}
MasterControllerAdapter.sharedInstance.sendEvent2MasterController(event: event) { resultEvent in
if let eventResult = resultEvent as? KSMGenerateOtpResultEvent {
let result = GenerateOTPModel.handleGenerateOtpResultEvent(status: eventResult.status)
let actionResult = ActionResult(title: "generateOtpModel.title".localized(table: localizedTable),
success: result.success,
error: result.errorText?.localized(table: localizedTable),
duration: 0,
event: eventResult)
if result.success {
let otpResult = GenerateOtpResult(otp: eventResult.otp)
completion(actionResult, otpResult)
} else {
completion(actionResult, nil)
}
}
}
}
Android/Kotlin
Kotlin Generate OTP
private fun performGenerateOtpEvent(userId: String, tenantId: String, pin: String, data: String) {
val generateOtpEvent: GenerateOtpEvent?
if (pin.isBlank() && userId.isBlank()) {
generateOtpEvent = GenerateOtpEvent(
data.toByteArray())
} else if (userId.isBlank()) {
generateOtpEvent = GenerateOtpEvent(
data.toByteArray(),
pin)
} else {
generateOtpEvent = GenerateOtpEvent(
data.toByteArray(),
pin,
UserIdentifier(tenantId, userId))
}
synchronousEventHandler.postEvent(generateOtpEvent)?.then { resultEvent ->
when (resultEvent) {
is GenerateOtpResultEvent -> {
// resultEvent.status
// resultEvent.retryCounter
// resultEvent.retryDelay
// resultEvent.otp
}
else -> {
//...
}
}
}
}