Skip to main content

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:

  1. Define ssms.allowOfflinePinVerification in the mc_config.json file
  2. 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:

  1. GenerateOtpEvent(byte[] optionalData, String pin, UserIdentifier userIdentifier): Can be used always.
  2. GenerateOtpEvent(byte[] optionalData, String pin): Can be used if the user is logged in online, or if a GenerateOtpEvent with a userIdentifier has been triggered previously.
  3. GenerateOtpEvent(byte[] optionalData): Can be used if allowOfflinePinVerification is set to true in mc_config.json and a GenerateOtpEvent was previously triggered with a userIdentifier 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 -> {
//...
}
}
}
}