Activation
In environments with IDP, the app is connecting to the IDP server in a web view to do the user registration with project specific user authentication and getting some IDP token. With this token the MC can activate the user account for this app installation with the Security Server. Activation code/pin as in Digitanium is not needed. In addition userId and tenantId is needed as in Digitanium solutions. See EventList for IDP activation. Note that in the KOBIL Shift Lite environment, you can choose between several authentication modes during activation.
Proceed with such an activation if you receive a StartResultEvent with ACTIVATION_REQUIRED as sdkState.
Activation event flow diagram for KOBIL Shift Lite
Here is an event flow diagram to detail what the event flow looks like for this process:
iOS/Swift (Activation)
First get astClientData from master controller.
// You have to provide KsUserIdentifier, you do not have your userId at the
// time of activation so you have to pass tenantId and your userId is empty.
// KsUserIdentifier(tenantId: "tenantId", userId: "")
public func getAstClientData(userIdentifer: KsUserIdentifier, completion: @escaping((KSMGetAstClientDataResultEvent) -> Void)){
let astClientData = KSMGetAstClientDataEvent(userIdentifier: userIdentifer)
self.masterControllerAdapter.sendEvent2MasterController(astClientData) { event in
guard let macroEvent = event as? KsMacroEvent else {return}
guard let resultEvent = macroEvent as? KSMGetAstClientDataResultEvent else{return}
completion(resultEvent)
}
}
After getting astClientData, now prepare a url with header that contains astClientData.
/// helper class to handle the IAM webpage
internal class MaverickWebPageHelper {
/// IMPORTANT: the redirect URI is hardcoded right now. this value must fit to the settings on the IAM system (KOBIL IDP).
static let REDIRECT_URI = redirectUrl
/// the response type value within the OpenID protocol
static let RESPONSE_TYPE = "code"
/// the response mode value within the OpenID protocol
static let RESPONSE_MODE = "fragment"
/// the base url
static let BASE_URL = baseUrl;
// the scope
static let SCOPE = "openid";
// the end point
static let END_POINT = "maverimport";
static func getMaverickWebpageUrlFor(
clientId: String, astData: KSMGetAstClientDataResultEvent) -> String {
var result = BASE_URL
result += "/auth/realms/";
result += END_POINT;
result += "/protocol/openid-connect/auth?";
result += "client_id" + "=" + clientId
result += "&"
result += "redirect_uri" + "=" + REDIRECT_URI
result += "&"
result += "scope" + "=" + SCOPE
result += "&"
result += "response_type" + "=" + RESPONSE_TYPE
result += "&"
result += "response_mode" + "=" + RESPONSE_MODE
result += "&"
result += "nonce" + "=" + UUID().uuidString.lowercased().replacingOccurrences(of: "-", with: "")
result += "&"
result += "code_challenge" + "=" + astData.codeChallange
result += "&"
result += "code_challenge_method" + "=" + astData.codeChallangeMethod
return result
}
}
// We have some defined clientId for this server
enum MaverickClientIdType{
case newUser
case existingUser
case loginUser
var clientId:String{
switch self {
case .newUser:
// The user is not register in your app with current environment, so the user have to register first and then activate.
return "IDPRegistration"
case .existingUser:
// The user was register in your app, and he install your app 2nd time.
return "IDPLogin"
case .loginUser:
// User was login and after some time he came again for new session or his token is expired.
return "IDPSubsequentLogin"
}
}
}
Request Headers
HTTP headers allow the client and the server to pass additional information with an HTTP request or response. An HTTP header consists of a case-insensitive name followed by a colon (:), and then its value. Any whitespace before the value is ignored. Custom proprietary headers have historically used an X- prefix, but this convention was deprecated in June 2012 due to the inconveniences caused when nonstandard fields became standard, as outlined in RFC 6648. Request headers provide more information about the resource to be fetched or about the client requesting the resource. In our case, request headers are used to identify the client that requested the URL. In a basic registration scenario, we use one field:
- X-KOBIL-ASTCLIENTDATA
This field is used for user identification when requesting the webpage. If you are registering or creating a new user, you only need to set the X-KOBIL-ASTCLIENTDATA
header because MC SDK does not know the clientID yet, as the user does not exist before registration.
For login flows or activation of already existing users, you must always use both fields:
X-KOBIL-ASTCLIENTDATA
andX-KOBIL-ASTCLIENTID
.
All required data can be found in GetAstClientDataResultEvent
.
Please take a look at example below
// Create MaverickWebPage URL request with headers
class MaverickWebViewUrlRequest {
static func getUrlRequest(userType: MaverickClientIdType,url:String,astData: KSMGetAstClientDataResultEvent) -> URLRequest?{
guard let webPageUrl = URL(string: url) else{return nil}
var request = URLRequest(url: webPageUrl)
switch userType {
case .newUser:
request.setValue(astData.clientData, forHTTPHeaderField: "X-KOBIL-ASTCLIENTDATA")
case .existingUser,.loginUser:
request.setValue(astData.clientId, forHTTPHeaderField: "X-KOBIL-ASTCLIENTID")
request.setValue(astData.clientData, forHTTPHeaderField: "X-KOBIL-ASTCLIENTDATA")
}
return request
}
}
SetAuthorizationCodeEvent
When you load this url in the web view and after registration and login you will receive authorization code from webView and you have to parse this authorization code from url and then you have to provide this authorization code to master controller sdk for getting a token and if MC gets the token then you will receive an status OK else some other status type according to the error. Refer to IAM Activation for the extraction of the authorization code from the url (see parseUrl() method).
//Set authorization code
public func setAuthorizationCode(userType: MaverickClientIdType, authorizationCode: String,userIdentifer: KsUserIdentifier,completion:@escaping((KSMSetAuthorisationCodeResultEvent) -> Void?)){
let setAuthorizationCodeEvent = KSMSetAuthorisationCodeEvent(userIdentifier: userIdentifer, authenticationMode: KSMAuthenticationMode.no, authorisationCode: authorizationCode, clientId: userType.clientId)
self.masterControllerAdapter.sendEvent2MasterController(setAuthorizationCodeEvent) { event in
guard let macroEvent = event as? KsMacroEvent else {return}
guard let resultEvent = macroEvent as? KSMSetAuthorisationCodeResultEvent else{return}
completion(resultEvent)
}
}
Android/Kotlin (Activation)
First get astClientData from master controller.
// You have to provide UserIdentifier,you do not have your userId at the time of activation so you have to pass tenantId and your userId is empty.
//UserIdentifier(tenantId: "<tenantId>", userId: "")
fun getAstClientData(userId: String, tenantId: String) : Future? {
val getAstClientDataEvent = GetAstClientDataEvent(UserIdentifier(tenantId, userId))
return mcEventHandler?.postEvent(getAstClientDataEvent)
}
After getting astClientData, now prepare a url with header that contains astClientData.
/// helper class to handle the IAM webpage
class MaverickHelper {
companion object {
private val REDIRECT_URI = "https://kobil/OpenIdRedirectUri"
private val RESPONSE_TYPE = "code"
private val RESPONSE_MODE = "fragment"
private val SCOPE = "openid"
private val TENANTID = KsAppConfigManager.getEntityInstance().tenantId!!
// the base url for the maverick login/activation webpage
private val BASE_URL = KsAppConfigManager.getEntityInstance().iam?.loginActivationWebpageUrl!!
/**
* This function builds the url for maverick login/activation webpage
* @param clientId: the clientId to be used in the url.
* It can have one of these values depending on the use case:
* * IDPRegistration: when activating/adding a new user
* * IDPLogin: when activating or performing the first login for an existing user
* * IDPSubsequentLogin: for all subsequent logins of an existing user
*/
fun getMaverickWebpageUrlFor(clientId: String, astData: GetAstClientDataResultEvent, context: Context): String {
var nonce = Settings.Secure.getString(context?.contentResolver, Settings.Secure.ANDROID_ID)!!.toLowerCase().replace("-", "")
var res = BASE_URL
res += "/auth/realms/$TENANTID/protocol/openid-connect/auth?"
res += "client_id=$clientId"
res += "&redirect_uri=$REDIRECT_URI"
res += "&scope=$SCOPE"
res += "&response_type=$RESPONSE_TYPE"
res += "&response_mode=$RESPONSE_MODE"
res += "&nonce=$nonce"
res += "&code_challenge=${astData.codeChallenge}"
res += "&code_challenge_method=${astData.codeChallengeMethod}"
return res
}
}
}
// trigger the GetAstClientDataEvent to get the needed information to populate the headers for
// the maverick login webpage
mcHandler?.getAstClientData(userId, tenantId)?.then {
if (it is GetAstClientDataResultEvent) {
logDebug("got AstClientDataResponse opening TWV", "handleStartType")
val headers: HashMap<String, String> = HashMap()
// for the first login we need to only populate the "X-KOBIL-ASTCLIENTDATA" entry with clientData
// got retrieved from the GetAstClientDataResultEvent
headers[AppConstants.kAstClientData] = it.clientData
navigatorUtil?.openTrustedWebView(
webUrl,
urlWhiteList,
trustedSslServerCerts,
getString(R.string.app_name),
headers
)
}
}
After entering your credentials on the IAM registration page, you will receive a URL from the web view that contains the authorization code. You need to parse the authorization code from the URL and provide it to the master controller sdk via SetAuthorizationCodeEvent. The MC will use the authorization code to exchange it for an IAM token. Upon successful key exchange you will receive a status OK. Refer to IAM Activation for the extraction of the authorization code from the url (see parseUrl() method).
//Set authorization code
fun setAuthorizationCode(userId: String, tenantId: String, authCode: String, clientId: String) {
val authMode = if (SessionManager.getInstance(context).useBiometry()) AuthenticationMode.BIOMETRIC else AuthenticationMode.NO
val setAuthCodeEvent = SetAuthorisationCodeEvent(UserIdentifier(tenantId, userId), authMode, authCode, clientId)
mcEventHandler?.postEvent(setAuthCodeEvent)?.then {
logDebug("received SetAuthorisationCodeResultEvent: $it", "setAuthorisationCode")
// handle result. StatusType.OK would mean the newly activated user is now logged in.
}
}