Login
When a StartResultEvent with LOGIN_REQUIRED as sdkState is received in response to a StartEvent, you should initiate the Login flow.
Note: When
LOGIN_REQUIREDis received after a restart, there are two valid approaches:
- Stay Logged In (
OfflineLoginEvent): if a valid offline or authorization-grant token is stored, post anOfflineLoginEventto re-authenticate without requiring credentials. See Stay Logged In.- Fresh IDP login: perform a full login using
getAuthorizationCodeas described below.There is no way to determine in advance which will succeed —
LOGIN_REQUIREDonly confirms the user is activated on the app client, not whether valid tokens are still present. The recommended approach is to attemptOfflineLoginEventfirst; if it returns a status other thanStatusType.OK, fall back to a fresh IDP login using the sameAuthenticationMode.
This involves calling IdpSdkNativeInterface.getAuthorizationCode and supplying the user's username and password via the IdpSdkInteractionInterface callback. Follow the three-step flow in the correct sequence to ensure a successful login.
IdpSdk Login Flow Diagram
The event flow diagram illustrates the sequence of events during the login process when using the IdpSdk directly.
Implementation Examples
iOS/Swift
Implement IdpSdkInteractionInterface to supply credentials when the SDK requests them. The SDK calls provideCredentials with the required input fields via interactionData.inputFieldIds — iterate over them and return a HashMap with the filled values.
Warning:
provideCredentialsis called from a separate thread. Your implementation must be thread-safe; any access to shared state in this scope must be synchronized. It may also be called multiple times in multi-step flows. See provideCredentials Behaviour for details on field IDs, multi-step flows, and error propagation.
extension IdpHandler: IdpSdkInteractionInterface {
/// Provides credentials when requested by the IDP SDK interaction flow.
///
/// - Parameter interactionData: Additional interaction context provided by the IDP SDK.
/// - Returns: Sample credential container.
func provideCredentials(_ interactionData: IdpSdkInteractionData) -> IdpSdkProvideCredentialsResult {
// To cancel the flow and propagate an error, create the error and return it:
// let error: IdpSdkError = .init(subsystem, errorCode, , errorDescription)
// return .init(error: error)
.init(credentials: ["username" : username,
"password" : password,
"activation-code": activationCode]
)
}
}
The full three-step activation flow:
func login() {
// Step 1 — get AST client data
let astClientData = await getAstClientData(tenantId: tenantId)
// Step 2 — get authorization code; SDK calls provideCredentials on a background thread
let authorizationCode = await getAuthorizationCode(
clientId: clientId,
astClientDataInfo: .init(astClientData: astClientData)
)
// Step 3 — pass authorization code to MC-SDK
let authorizationCodeResult = await setAuthorizationCode(
tenantId: tenantId,
authCode: authorizationCode,
authMode: .no,
clientId: clientId
)
}
...
// Functions implementing the steps
/// Requests AST client data required to obtain an authorization code.
///
/// - Parameter tenantId: Tenant identifier used to build the `KSMGetAstClientDataEvent`.
/// - Returns: `KSMGetAstClientDataResultEvent` containing AST client data and challenge information.
func getAstClientData(tenantId: String) async -> KSMGetAstClientDataResultEvent {
let event = KSMGetAstClientDataEvent(tenantId: tenantId)
let result = await masterController.receive(event) as! KSMGetAstClientDataResultEvent
return result
}
/// Requests an authorization code from the IDP SDK.
///
/// - Parameters:
/// - clientId: Client identifier used in the IDP authorization request.
/// - astClientDataInfo: AST client data required by the IDP SDK to build the request.
/// - Returns: Authorization code returned by the IDP SDK, or an empty string if no code is provided.
func getAuthorizationCode(clientId: String, astClientDataInfo: AstClientDataInfo) async -> String {
// userId must be the userId of the (existing) user that we want to log in.
await withCheckedContinuation { continuation in
idpSdk.getAuthorizationCode(
withAstClientData: astClientDataInfo,
clientId: clientId,
interactionInterface: self,
userId: userId,
traceParent: ""
) { result in
if let code = result.authorizationCode {
continuation.resume(returning: code)
} else {
continuation.resume(returning: "")
}
}
}
}
/// Submits the authorization code to MCSDK.
///
/// - Parameters:
/// - tenantId: Tenant identifier used to build the `KSMSetAuthorisationCodeEvent`.
/// - authCode: Authorization code received from the IDP SDK.
/// - authMode: Authentication mode used when submitting the authorization code.
/// - clientId: Client identifier associated with the authorization request.
/// - Returns: `KSMSetAuthorisationCodeResultEvent` describing the outcome of the operation.
func setAuthorizationCode(
tenantId: String,
authCode: String,
authMode: KSMAuthenticationMode,
clientId: String
) async -> KSMSetAuthorisationCodeResultEvent {
let event = KSMSetAuthorisationCodeEvent(
tenantId: tenantId,
authenticationMode: authMode,
authorisationCode: authCode,
clientId: clientId
)
let result = await masterController.receive(event) as! KSMSetAuthorisationCodeResultEvent
return result
}
NOTE: The
userIdpassed togetAuthorizationCodeduring login should be the enrolled user's identifier, obtained from theStartResultEventorRestartResultEventuser list.
Android/Kotlin
Implement IdpSdkInteractionInterface to supply credentials when the SDK requests them. The SDK calls provideCredentials with the required input fields via interactionData.inputFieldIds — iterate over them and return a HashMap with the filled values.
Warning:
provideCredentialsis called from a separate thread. Your implementation must be thread-safe; any access to shared state in this scope must be synchronized. It may also be called multiple times in multi-step flows. See provideCredentials Behaviour for details on field IDs, multi-step flows, and error propagation.
class LoginHandler {
companion object : IdpSdkInteractionInterface {
const val LOGIN_CLIENT = "KssIdpLogin"
// Called by the SDK from a background thread to collect user credentials.
// Must be thread-safe.
override fun provideCredentials(interactionData: IdpSdkInteractionData): IdpSdkProvideCredentialsResult {
val credentials = HashMap<String?, String?>()
for (fieldId in interactionData.inputFieldIds) {
when (fieldId) {
IdpSdkConstants.USERNAME -> credentials[fieldId] = "<username>"
IdpSdkConstants.PASSWORD -> credentials[fieldId] = "<password>"
}
}
// To cancel the flow and propagate an error, return:
// IdpSdkProvideCredentialsResult(IdpSdkError(subsystem, errorCode, errorDescription))
return IdpSdkProvideCredentialsResult(credentials)
}
}
}
The full three-step login flow:
fun login() {
// Step 1 — get AST client data from the MC-SDK
val getAstClientDataEvent = GetAstClientDataEvent(IdpSdkApp.TENANT_ID)
MasterController.getInstance()?.postEvent(getAstClientDataEvent)?.then { resultEvent ->
when (resultEvent) {
is GetAstClientDataResultEvent -> {
if (resultEvent.status != StatusType.OK) {
Log.e(TAG, "GetAstClientData failed: ${resultEvent.errorDescription}")
return@then
}
// Step 2 — get authorization code from the IdpSdk
val astClientDataInfo = AstClientDataInfo(
resultEvent.clientData,
resultEvent.astClientId,
resultEvent.codeChallenge,
resultEvent.codeChallengeMethod
)
// userId is the identifier of the enrolled user (from StartResultEvent)
val userId: String? = StartRestartHandler.getFirstUserId()
IdpSdkNativeInterface.getAuthorizationCode(
astClientDataInfo,
LOGIN_CLIENT,
this, // IdpSdkInteractionInterface — provideCredentials called here
userId,
traceParent // optional W3C-compliant trace parent, or null
)?.thenApply { idpSdkResult ->
if (idpSdkResult.hasError()) {
Log.e(TAG, "getAuthorizationCode failed: ${idpSdkResult.idpSdkError}")
return@thenApply
}
// Step 3 — pass the authorization code to the MC-SDK
val authorizationCode = idpSdkResult.authorizationCode
val setEvent = SetAuthorisationCodeEvent(
IdpSdkApp.TENANT_ID,
AuthenticationMode.BIOMETRIC,
authorizationCode,
LOGIN_CLIENT
)
MasterController.getInstance()?.postEvent(setEvent)?.then { setResult ->
when (setResult) {
is SetAuthorisationCodeResultEvent -> {
if (setResult.status == StatusType.OK) {
Log.i(TAG, "Login successful")
} else {
Log.e(TAG, "SetAuthorisationCode failed: ${setResult.errorDescription}")
}
}
else -> error("Unexpected result for SetAuthorisationCodeEvent")
}
}
}
}
else -> error("Unexpected result for GetAstClientDataEvent")
}
}
}
NOTE: The
userIdpassed togetAuthorizationCodeduring login should be the enrolled user's identifier, obtained from theStartResultEventorRestartResultEventuser list.
Request Parameters
The following credential fields are requested by the IdpSdk during login via provideCredentials:
IdpSdkConstants.USERNAME — the user's username
IdpSdkConstants.PASSWORD — the user's password
The clientId passed to getAuthorizationCode is provided by the KOBIL IDP services (e.g. KssIdpLogin).
Important Notes
For more information about MC-SDK event usage during IdpSdk flows, please refer to GetAstClientData and SetAuthorisationCode.
Response Handling (iOS/Swift)
IdpSdkNativeInterface.getAuthorizationCode returns a CompletableFuture<IdpSdkResult>. Check idpSdkResult.hasError() before accessing idpSdkResult.authorizationCode.
In response to the SetAuthorisationCodeEvent, the MC-SDK sends a SetAuthorisationCodeResultEvent with the appropriate status.
Response Handling (Android/Kotlin)
IdpSdkNativeInterface.getAuthorizationCode returns a CompletableFuture<IdpSdkResult>. Check idpSdkResult.hasError() before accessing idpSdkResult.authorizationCode.
In response to the SetAuthorisationCodeEvent, the MC-SDK sends a SetAuthorisationCodeResultEvent with the appropriate status.