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.allowOfflinePinVerification
in themc_config.json
file - 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,
GenerateOtpEvent
can be triggered without a PIN. - false: The PIN will never be cached and each time a
GenerateOtpEvent
is 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 aGenerateOtpEvent
with auserIdentifier
has been triggered previously.GenerateOtpEvent(byte[] optionalData)
: Can be used ifallowOfflinePinVerification
is set totrue
inmc_config.json
and aGenerateOtpEvent
was previously triggered with auserIdentifier
and 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
private var localizedTable = "GenerateOtpModel"
class GenerateOTPModel {
static let sharedInstance: GenerateOTPModel = {
var sharedInstance = GenerateOTPModel()
return sharedInstance
}()
func generateOTP(otpRequest: GenerateOtpRequest, completion: @escaping (ActionResult, GenerateOtpResult?) -> Void) {
performGenerateOtp(otpRequest: otpRequest) { actionResult, otpResult in
completion(actionResult, otpResult)
}
}
func performGenerateOtp(otpRequest: GenerateOtpRequest, completion: @escaping (ActionResult, GenerateOtpResult?) -> Void) {
let userIdentifier = KsUserIdentifier(tenantId: otpRequest.tenant, userId: otpRequest.user)
let event: KSMGenerateOtpEvent
event = KSMGenerateOtpEvent(optionalData: otpRequest.data, userIdentifier: userIdentifier, pin: otpRequest.pin)
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)
}
}
}
}
static let resultDict: [KSMEventStatusType: EventResultState] =
[.KSMOK: EventResultState(success: true, errorText: nil),
.KSMFAILED: EventResultState(success: false, errorText: "failed"),
.KSMINTERNAL_ERROR: EventResultState(success: false, errorText: "internal error")]
static func handleGenerateOtpResultEvent(status: KSMEventStatusType) -> EventResultState {
guard let state = resultDict[status] else {
return EventResultState(success: false, errorText: "failed")
}
return state
}
}
struct GenerateOtpRequest {
var pin: String
var data: Data
var user: String
var tenant: String
}
struct GenerateOtpResult {
var otp: String?
}
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 -> {
//...
}
}
}
}