Tutorial: Sign in users in Android (Kotlin) mobile app
This is the third tutorial in the tutorial series that guides you on signing in users using Microsoft Entra External ID.
In this tutorial, you'll:
- Sign in user
- Sign out user
Prerequisites
Tutorial: Prepare your Android app for authentication.
Sign in user
You have two main options for signing in users using Microsoft Authentication Library (MSAL) for Android: acquiring tokens interactively or silently.
To sign in user interactively, use the following code:
private fun acquireTokenInteractively() { binding.txtLog.text = "" if (account != null) { Toast.makeText(this, "An account is already signed in.", Toast.LENGTH_SHORT).show() return } /* Extracts a scope array from text, i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"] */ val scopes = scopes.lowercase().split(" ") val parameters = AcquireTokenParameters.Builder() .startAuthorizationFromActivity(this@MainActivity) .withScopes(scopes) .withCallback(getAuthInteractiveCallback()) .build() authClient.acquireToken(parameters) }
The code initiates the process of acquiring a token interactively using MSAL for Android. It first clears the text log field. Then, it checks if there's already a signed-in account, if so, it displays a toast message indicating that an account is already signed in and returns.
Next, it extracts scopes from text input and converts them to lowercase before splitting them into an array. Using these scopes, it builds parameters for acquiring a token, including starting the authorization process from the current activity and specifying a callback. Finally, it calls
acquireToken()
on the authentication client with the constructed parameters to initiate the token acquisition process.In the code, where we specify our callback, we use a function called
getAuthInteractiveCallback()
. The function should have the following code:private fun getAuthInteractiveCallback(): AuthenticationCallback { return object : AuthenticationCallback { override fun onSuccess(authenticationResult: IAuthenticationResult) { /* Successfully got a token, use it to call a protected resource - Web API */ Log.d(TAG, "Successfully authenticated") Log.d(TAG, "ID Token: " + authenticationResult.account.claims?.get("id_token")) Log.d(TAG, "Claims: " + authenticationResult.account.claims /* Reload account asynchronously to get the up-to-date list. */ CoroutineScope(Dispatchers.Main).launch { accessToken = authenticationResult.accessToken getAccount() binding.txtLog.text = getString(R.string.log_token_interactive) + accessToken } } override fun onError(exception: MsalException) { /* Failed to acquireToken */ Log.d(TAG, "Authentication failed: $exception") accessToken = null binding.txtLog.text = getString(R.string.exception_authentication) + exception if (exception is MsalClientException) { /* Exception inside MSAL, more info inside MsalError.java */ } else if (exception is MsalServiceException) { /* Exception when communicating with the STS, likely config issue */ } } override fun onCancel() { /* User canceled the authentication */ Log.d(TAG, "User cancelled login."); } } }
The code snippet defines a function,
getAuthInteractiveCallback
, which returns an instance ofAuthenticationCallback
. Within this function, an anonymous class implementing theAuthenticationCallback
interface is created.When authentication succeeds (
onSuccess
), it logs the successful authentication, retrieves the ID token and claims, updates the access token asynchronously usingCoroutineScope
, and updates the UI with the new access token. The code retrieves the ID token from theauthenticationResult
and logs it. Claims in the token contain information about the user, such as their name, email, or other profile information. You can retrieve the claims associated with the current account by accessingauthenticationResult.account.claims
.If there's an authentication error (
onError
), it logs the error, clears the access token, updates the UI with the error message, and provides more specific handling forMsalClientException
andMsalServiceException
. If the user cancels the authentication (onCancel
), it logs the cancellation.Make sure you include the import statements. Android Studio should include the import statements for you automatically.
To sign in user silently, use the following code:
private fun acquireTokenSilently() { binding.txtLog.text = "" if (account == null) { Toast.makeText(this, "No account available", Toast.LENGTH_SHORT).show() return } /* Extracts a scope array from text, i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"] */ val scopes = scopes.lowercase().split(" ") val parameters = AcquireTokenSilentParameters.Builder() .forAccount(account) .fromAuthority(account!!.authority) .withScopes(scopes) .forceRefresh(false) .withCallback(getAuthSilentCallback()) .build() authClient.acquireTokenSilentAsync(parameters) }
The code initiates the process of acquiring a token silently. It first clears the text log. Then, it checks if there's an available account; if not, it displays a toast message indicating this and exits. Next, it extracts scopes from text input, converts them to lowercase, and splits them into an array.
Using these scopes, it constructs parameters for acquiring a token silently, specifying the account, authority, scopes, and callback. Finally, it asynchronously triggers
acquireTokenSilentAsync()
on the authentication client with the constructed parameters, starting the silent token acquisition process.In the code, where we specify our callback, we use a function called
getAuthSilentCallback()
. The function should have the following code:private fun getAuthSilentCallback(): SilentAuthenticationCallback { return object : SilentAuthenticationCallback { override fun onSuccess(authenticationResult: IAuthenticationResult?) { Log.d(TAG, "Successfully authenticated") /* Display Access Token */ accessToken = authenticationResult?.accessToken binding.txtLog.text = getString(R.string.log_token_silent) + accessToken } override fun onError(exception: MsalException?) { /* Failed to acquireToken */ Log.d(TAG, "Authentication failed: $exception") accessToken = null binding.txtLog.text = getString(R.string.exception_authentication) + exception when (exception) { is MsalClientException -> { /* Exception inside MSAL, more info inside MsalError.java */ } is MsalServiceException -> { /* Exception when communicating with the STS, likely config issue */ } is MsalUiRequiredException -> { /* Tokens expired or no session, retry with interactive */ } } } } }
The code defines a callback for silent authentication. It implements the
SilentAuthenticationCallback
interface, overriding two methods. In theonSuccess
method, it logs successful authentication and displays the access token.In the
onError
method, it logs authentication failure, handles different types of exceptions, such asMsalClientException
andMsalServiceException
, and suggests retrying with interactive authentication if needed.Make sure you include the import statements. Android Studio should include the import statements for you automatically.
Sign out
To sign out a user from your Android (Kotlin) app using MSAL for Android, use the following code:
private fun removeAccount() {
binding.userName.text = ""
binding.txtLog.text = ""
authClient.signOut(signOutCallback())
}
The code removes an account from the application. It clears the displayed user name and text log. Then, it triggers the sign out process using the authentication client, specifying a sign out callback to handle the completion of the sign out operation.
In the code, where we specify our callback, we use a function called signOutCallback()
. The function should have the following code:
private fun signOutCallback(): ISingleAccountPublicClientApplication.SignOutCallback {
return object : ISingleAccountPublicClientApplication.SignOutCallback {
override fun onSignOut() {
account = null
updateUI(account)
}
override fun onError(exception: MsalException) {
binding.txtLog.text = getString(R.string.exception_remove_account) + exception
}
}
}
The code defines a sign out callback for a single account in the public client application. It implements the ISingleAccountPublicClientApplication.SignOutCallback
interface, overriding two methods.
In the onSignOut
method, it nullifies the current account and updates the user interface accordingly. In the onError
method, it logs any errors that occur during the sign out process and updates the text log with the corresponding exception message.
Make sure you include the import statements. Android Studio should include the import statements for you automatically.