Authentication State

Prev Next

Authentication Analytics

The Authentication SDK provides callbacks so you can track authentication state changes — configuration, login, token refresh, and logout. You can use these to update your UI, forward events to your analytics platform, or trigger downstream logic.

Both iOS and Android expose a state machine with nearly identical states. The key difference is the delivery mechanism: iOS uses a delegate protocol, while Android uses LiveData.


Overview

Aspect

iOS

Android

State API

TMAuthenticationDelegate protocol

TMAuthentication.getLoginStateLiveData()

Delivery

Delegate callback with ServiceState enum

LiveData<ServiceLoginState> observer

State enum

TMAuthentication.ServiceState (14 states)

ServiceLoginState (15 states — includes LOGIN_EXCHANGING)

Backend context

BackendService? parameter on each callback

Not provided per-state; query with getToken(AuthSource)

Error context

Error? parameter on each callback

Not provided per-state; use ActivityResult for login outcome

Federated login details

States only (e.g., loginLinkAccountPresented)

States + UserAnalyticsDelegate events for button-level tracking


Service States

Both platforms share the same state flow. States are grouped into four phases.

Configuration States

These fire during SDK initialization (TMAuthentication.Builder.build()) before any user interaction. During configuration, multiple backend services (Host, Archtics, MicroFlex) may be configured, causing multiple serviceConfigured callbacks — one per backend. serviceConfigurationCompleted fires only once when all backends have finished configuring.

iOS (ServiceState)

Android (ServiceLoginState)

Description

serviceConfigurationStarted

SERVICE_CONFIGURATION_STARTED

Configuration has started.

serviceConfigured

SERVICE_CONFIGURED

A specific backend has been configured. May fire multiple times.

serviceConfigurationCompleted

SERVICE_CONFIGURATION_COMPLETED

All backends have finished configuring. The SDK is ready for login.

Querying configured services:

You can check which services were configured at any time:

// Check which backends are configured via TMAuthentication.BackendService
// The backend parameter in onStateChanged tells you which service was configured
// Check which backends are configured via the configuration object
val config = tmAuthentication.configuration
val hasHost = config.modernAccounts?.host != null
val hasArchtics = config.modernAccounts?.archtics != null
val hasMfx = config.modernAccounts?.mfx != null

Login States

These fire during the login flow. During login, multiple services may be logged in (for example, Archtics and Host). The loggedIn state may fire multiple times — once per backend service. loginCompleted fires only once when the entire login process (including link-account flows) has finished.

iOS (ServiceState)

Android (ServiceLoginState)

Description

loginStarted

LOGIN_STARTED

Login has started.

—

LOGIN_EXCHANGING

(Android only) Token exchange is in progress.

loginPresented

LOGIN_PRESENTED

A login page has been presented for a backend.

loggedIn

LOGGED_IN

The user has logged in to a specific backend. May fire multiple times.

loginAborted

LOGIN_ABORTED

The user manually cancelled the login.

loginFailed

LOGIN_FAILED

Login failed for a specific backend.

loginLinkAccountPresented

LOGIN_LINK_ACCOUNT_PRESENTED

A link-account prompt has been presented.

loginCompleted

LOGIN_COMPLETED

The entire login process has completed.

Querying logged-in services:

// The backend parameter in onStateChanged tells you which service was logged in

// Check which backends have valid tokens
AuthSource.entries.forEach { source ->
    val token = tmAuthentication.getToken(source)
    if (token != null) {
        Log.d("Auth", "$source is logged in")
    }
}

Token Refresh States

iOS (ServiceState)

Android (ServiceLoginState)

Description

tokenRefreshed

TOKEN_REFRESHED

The user's auth token has been refreshed. May fire multiple times during the app's lifetime.

Logout States

When you call logout, by default the system logs out of all currently configured services. This means the loggedOut state may fire multiple times — once per backend. logoutCompleted fires only once at the end.

iOS (ServiceState)

Android (ServiceLoginState)

Description

logoutStarted

LOGOUT_STARTED

Logout has started.

loggedOut

LOGGED_OUT

The user has logged out of a specific backend. May fire multiple times.

logoutCompleted

LOGOUT_COMPLETED

The entire logout process has completed.


Setup

iOS

Set yourself as the delegate on the shared TMAuthentication instance:

TMAuthentication.shared.delegate = self

Then conform to TMAuthenticationDelegate:

extension MyViewController: TMAuthenticationDelegate {

    func onStateChanged(
        backend: TMAuthentication.BackendService?,
        state: TMAuthentication.ServiceState,
        error: (Error)?) {

        // Handle state change
    }
}

Parameters:

Name

Type

Description

backend

TMAuthentication.BackendService?

The backend service (Host, Archtics, etc.) the state relates to. nil for global state changes.

state

TMAuthentication.ServiceState

The current authentication state.

error

Error?

An error, if one occurred during the state transition.

Note: Always include an @unknown default case to handle new states that may be added in future SDK versions.

Android

Observe the login state LiveData from your TMAuthentication instance:

tmAuthentication.getLoginStateLiveData().observe(this) { state ->
    Log.d("Auth", "State changed: $state")
}

Android also provides login outcomes via ActivityResult:

private val loginLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    when (result.resultCode) {
        Activity.RESULT_OK -> handleLoginSuccess()
        Activity.RESULT_CANCELED -> handleLoginCancelled()
    }
}

Note: Use both mechanisms together — getLoginStateLiveData() gives you granular state transitions, while ActivityResult gives you the final login outcome.


Federated Login Events (Android)

In addition to the ServiceLoginState state machine, the Android SDK emits button-level federated login analytics through UserAnalyticsDelegate. These provide more detail about user interactions during the link-account flow:

Action

Description

ACTION_FED_LOGIN_LINK_ACCOUNTS_SCREEN_SHOWED

Link-accounts screen was presented

ACTION_FED_LOGIN_LINK_ACCOUNTS_SCREEN_DISMISSED

Link-accounts screen was dismissed

ACTION_FED_LOGIN_SCREEN_DISMISSED_AFTER_SUCCESS_LOGIN_NO_LINK

Login dismissed after success without linking

ACTION_FED_LOGIN_LINK_ACCOUNTS_BUTTON_PRESSED

User pressed "Link Accounts"

ACTION_FED_LOGIN_NO_THANKS_BUTTON_PRESSED

User pressed "No Thanks"

Note: These actionName strings are prefixed with com.ticketmaster.presencesdk.eventanalytic.action..

UserAnalyticsDelegate.handler.observe(this) { data ->
    data?.let {
        if (it.actionName.contains("FED_LOGIN")) {
            Log.d("AuthAnalytics", "Federated login event: ${it.actionName}")
        }
    }
}

Session Expiration

iOS

Session expiration is handled via TMTicketsOrderDelegate. See Tickets SDK Analytics for details.

Android

Observe session expiration (401 errors) via the sessionExpiredDelegate Flow:

lifecycleScope.launch {
    TicketsSDKSingleton.sessionExpiredDelegate.collect {
        Log.d("Auth", "Session expired")
        TicketsSDKSingleton.logout {
            launchLogin()
        }
    }
}

Complete Examples

iOS

TMAuthentication.shared.delegate = self

extension MyViewController: TMAuthenticationDelegate {

    func onStateChanged(
        backend: TMAuthentication.BackendService?,
        state: TMAuthentication.ServiceState,
        error: (Error)?) {

        if let backend = backend {
            if let error = error {
                print("Auth: .\(state.rawValue) backend: \(backend.description) error: \(error.localizedDescription)")
            } else {
                print("Auth: .\(state.rawValue) backend: \(backend.description)")
            }
        } else {
            if let error = error {
                print("Auth: .\(state.rawValue) error: \(error.localizedDescription)")
            } else {
                print("Auth: .\(state.rawValue)")
            }
        }

        switch state {
        // Configuration
        case .serviceConfigurationStarted:
            break
        case .serviceConfigured:
            break
        case .serviceConfigurationCompleted:
            // SDK is ready — you can now trigger login
            break

        // Login
        case .loginStarted:
            break
        case .loginPresented:
            break
        case .loggedIn:
            break
        case .loginAborted:
            break
        case .loginFailed:
            if let error = error {
                print("Login failed: \(error.localizedDescription)")
            }
        case .loginLinkAccountPresented:
            break
        case .loginCompleted:
            // Login fully complete — safe to update UI
            break

        // Token
        case .tokenRefreshed:
            break

        // Logout
        case .logoutStarted:
            break
        case .loggedOut:
            break
        case .logoutCompleted:
            // Logout fully complete — update UI
            break

        @unknown default:
            break
        }
    }
}

Android

class MainActivity : AppCompatActivity() {

    private lateinit var tmAuthentication: TMAuthentication

    private val loginLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        when (result.resultCode) {
            Activity.RESULT_OK -> showEventsScreen()
            Activity.RESULT_CANCELED -> Log.d("Auth", "Login cancelled or failed")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setupSDK()
    }

    private fun setupSDK() {
        lifecycleScope.launch {
            TMAuthentication.Builder(
                apiKey = BuildConfig.CONSUMER_KEY,
                clientName = BuildConfig.TEAM_NAME
            )
                .region(TMXDeploymentRegion.US)
                .environment(TMXDeploymentEnvironment.Production)
                .build(this@MainActivity)
                .fold(
                    onSuccess = { auth ->
                        tmAuthentication = auth
                        observeAuthState()
                    },
                    onFailure = { error ->
                        Log.e("Auth", "Configuration failed: ${error.message}")
                    }
                )
        }
    }

    private fun observeAuthState() {
        // Observe granular state transitions
        tmAuthentication.getLoginStateLiveData().observe(this) { state ->
            Log.d("Auth", "State: $state")

            when (state) {
                ServiceLoginState.SERVICE_CONFIGURATION_COMPLETED -> {
                    // SDK is ready — launch login
                    launchLogin()
                }
                ServiceLoginState.LOGIN_COMPLETED -> {
                    // Login fully complete
                    showEventsScreen()
                }
                ServiceLoginState.LOGIN_FAILED -> {
                    Log.e("Auth", "Login failed")
                }
                ServiceLoginState.LOGIN_LINK_ACCOUNT_PRESENTED -> {
                    Log.d("Auth", "Link account screen presented")
                }
                ServiceLoginState.LOGOUT_COMPLETED -> {
                    // Logout complete — return to login screen
                    launchLogin()
                }
                else -> { }
            }
        }
    }

    private fun launchLogin() {
        loginLauncher.launch(tmAuthentication.getLoginIntent(this))
    }

    private fun showEventsScreen() {
        TicketsSDKSingleton.getEventsFragment(this)?.let { fragment ->
            supportFragmentManager.beginTransaction()
                .replace(R.id.fragmentContainer, fragment)
                .commit()
        }
    }
}

Common Patterns

Waiting for Login to Complete

On both platforms, wait for loginCompleted / LOGIN_COMPLETED before updating your UI. The loggedIn / LOGGED_IN state fires per-backend and login may still be in progress.

Handling Multiple Backends

During login with multiple backends (e.g., Host + Archtics), you will see a sequence like:

LOGIN_STARTED
LOGIN_PRESENTED     (for Archtics)
LOGGED_IN           (for Archtics)
LOGIN_PRESENTED     (for Host)
LOGGED_IN           (for Host)
LOGIN_COMPLETED     (once, at the end)

The same pattern applies to configuration and logout — intermediate states fire per-backend, while the *_COMPLETED state fires once at the end.


Related Documentation