Skip to content

Anonymous User ID Authentication

This guide explains how to enable and integrate anonymous user id authentication with MimiCore inside MimiSDK.

Introduction

In Android MimiSDK 7.0.0, we introduced the ability to login an anonymous user using their anonymous user id.

The main use cases for this are partner integrations which want to support the possibility to associate an anonymous Mimi user with their own user account.

This would allow the anonymous Mimi user profile to: - Exist on multiple partner integrated products or, - To be restored on an app following a reinstall.

Building an integration

This section describes the individual steps involved for setting up a anonymous user id authentication flow with the MimiSDK.

This is an overview of the process:

Anonymous Id Login Workflow

Initializing MimiCore with allowAnonymousUserOnly=true

For data protection reasons it is essential when using anonymous user id authentication, that anonymous users cannot become non-anonymous Mimi users.

Enabling the MimiConfiguration option allowAnonymousUserOnly will disable MimiSDK UI support for Mimi account login/signup.

When starting MimiCore, you will need to pass in an instance of MimiConfiguration with the flag allowAnonymousUserOnly set to true:

MimiCore.start(context, clientId, clientSecret, 
                MimiConfiguration(allowAnonymousUserOnly = true))

Login using anonymous id

If you already have an anonymous id to login from a previous session, then you should do so before opening the MimiSDK UI screens.

coroutineScope.launch {
    val user = MimiCore.userController
                       .authenticate(MimiAuthRoute.LoginAnonymously(validAnonymousIdToLogin))
}
Here validAnonymousIdToLogin is the anonymousId which you have previously associated with your user and coroutineScope is some Kotlin coroutine scope defined by you.

Note: Make sure the anonymous id is not blank or empty string.

Note: authenticate is a network call and will propagate any exceptions raised by it.

Observing anonymous Id changes in the MimiSDK

The anonymous user is automatically created by the MimiSDK while the user onboards their Mimi profile.

This process happens asynchronously, so you need to observe changes in the MimiUser and store the anonymousId value to associate it with your current app user.

coroutineScope.launch {
    MimiCore.userController.mimiUser.observe {
        val anonymousIdToStore = it.value?.anonymousId
        TODO("Store this anonymousId to associate with your own user and login again in another session")
    }
}

Note: observe does not terminate, so you will need to control its execution through its coroutine Job.

Note: anonymousId will initially be null on a clean start.

Samples

The following code sample may be useful to handle some of the details when performing the login step.

import io.mimi.sdk.core.MimiCore
import io.mimi.sdk.core.model.MimiAuthRoute

/*
 * Sample code showing the key pieces of logic required when restoring a MimiUser via their
 * anonymousId.
 */
class MimiAuthManager {

    /**
     * MimiAnonLoginResult
     *
     * Success - The given anonymousId has been successfully logged in, or was already logged in.
     * Failure - There was a failure while logging in the anonymous user.
     * Skipped - There was no action performed, as there's no valid user to login.
     *
     */
    sealed class MimiAnonLoginResult {
        data class Success(val anonymousId: String) : MimiAnonLoginResult()
        data class Failure(val exception: Exception) : MimiAnonLoginResult()
        object Skipped : MimiAnonLoginResult()
    }

    /**
     * Attempts to authenticate an anonymousId, if it is defined.
     *
     * @param anonymousIdToLogin the anonymousId currently associated with your user.
     * @return See [MimiAnonLoginResult].
     */
    internal suspend fun authenticateAnonymousUser(anonymousIdToLogin: String?): MimiAnonLoginResult {

        // Important: Do not perform operation without allowAnonymousUserOnly!
        if (!MimiCore.configuration.allowAnonymousUserOnly) {
            return MimiAnonLoginResult
                .Failure(IllegalArgumentException("Logging in an anonymous user is not supported unless MimiConfiguration allowAnonymousUserOnly=true"))
        }

        if (!anonymousIdToLogin.isAnonymousIdValidFormat()) {
            // Nothing to restore, so return Skipped.
            return MimiAnonLoginResult.Skipped
        }

        // Proceed with login
        requireNotNull(anonymousIdToLogin) { "anonymousIdToLogin shouldn't be null here" }
        return authenticateValidAnonymousUserId(anonymousIdToLogin)

    }

    private suspend fun authenticateValidAnonymousUserId(validAnonymousIdToLogin: String): MimiAnonLoginResult {

        if (isAlreadyLoggedIn(validAnonymousIdToLogin)) {
            // Already have the correct user logged in - don't need to do anything.
            return MimiAnonLoginResult.Success(validAnonymousIdToLogin)
        }

        if (isDifferentUserLoggedIn(validAnonymousIdToLogin)) {
            // Logout existing MSDK user
            MimiCore.userController.logout()
        }

        // Login with the anonymous id
        return try {
            val user = MimiCore.userController
                .authenticate(MimiAuthRoute.LoginAnonymously(validAnonymousIdToLogin))
            MimiAnonLoginResult.Success(user.anonymousId)
        } catch (e: Exception) {
            MimiAnonLoginResult.Failure(e)
        }

    }

    private fun isAlreadyLoggedIn(validAnonymousIdToLogin: String): Boolean {
        return MimiCore.userController.mimiUser.state.value?.anonymousId == validAnonymousIdToLogin
    }

    private fun isDifferentUserLoggedIn(validAnonymousIdToLogin: String): Boolean {
        val currentMsdkAnonymousId = MimiCore.userController.mimiUser.state.value?.anonymousId
        return (!currentMsdkAnonymousId.isAnonymousIdValidFormat() && currentMsdkAnonymousId != validAnonymousIdToLogin)
    }

    // AnonymousId validity in terms of format only.
    private fun String?.isAnonymousIdValidFormat(): Boolean {
        return this?.isNotEmpty() ?: false
    }
}