Muokkaa

Jaa


Enable authentication in your own Android app by using Azure AD B2C

This article shows you how to add Azure Active Directory B2C (Azure AD B2C) authentication to your own Android mobile application.

Use this article with Configure authentication in a sample Android app by using Azure AD B2C, substituting the sample Android app with your own Android app. After you've completed the instructions in this article, your application will accept sign-ins via Azure AD B2C.

Prerequisites

Review the prerequisites and integration instructions in Configure authentication in a sample Android app by using Azure AD B2C.

Create an Android app project

If you don't already have an Android application, set up a new project by doing the following:

  1. In Android Studio, select Start a new Android Studio project.
  2. Select Basic Activity, and then select Next.
  3. Name your application.
  4. Save the package name. You'll enter it later in the Azure portal.
  5. Change the language from Kotlin to Java.
  6. Set the Minimum API level to API 19 or higher, and then select Finish.
  7. In the project view, choose Project in the dropdown list to display source and non-source project files, open app/build.gradle, and then set targetSdkVersion to 28.

Step 1: Install the dependencies

In the Android Studio project window, go to app > build.gradle, and then add the following:

apply plugin: 'com.android.application'

allprojects {
    repositories {
    mavenCentral()
    google()
    mavenLocal()
    maven {
        url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1'
    }
    maven {
        name "vsts-maven-adal-android"
        url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1"
        credentials {
            username System.getenv("ENV_VSTS_MVN_ANDROIDADAL_USERNAME") != null ? System.getenv("ENV_VSTS_MVN_ANDROIDADAL_USERNAME") : project.findProperty("vstsUsername")
            password System.getenv("ENV_VSTS_MVN_ANDROIDADAL_ACCESSTOKEN") != null ? System.getenv("ENV_VSTS_MVN_ANDROIDADAL_ACCESSTOKEN") : project.findProperty("vstsMavenAccessToken")
        }
    }
    jcenter()
    }
}
dependencies{
    implementation 'com.microsoft.identity.client:msal:2.+'
    }
packagingOptions{
    exclude("META-INF/jersey-module-version")
}

Step 2: Add the authentication components

The sample code is made up of the following components. Add these components from the sample Android app to your own app.

Component Type Source Description
B2CUser Class Kotlin Java Represents a B2C user. This class allows users to sign in with multiple policies.
B2CModeFragment Fragment class Kotlin Java A fragment represents a modular portion of the sign-in with the Azure AD B2C user interface within your main activity. This fragment contains most of the authentication code.
fragment_b2c_mode.xml Fragment layout Kotlin Java Defines the structure for a user interface for the B2CModeFragment fragment component.
B2CConfiguration Class Kotlin Java A configuration file contains information about your Azure AD B2C identity provider. The mobile app uses this information to establish a trust relationship with Azure AD B2C, sign users in and out, acquire tokens, and validate them. For more configuration settings, see the auth_config_b2c.json file.
auth_config_b2c.json JSON file Kotlin Java A configuration file contains information about your Azure AD B2C identity provider. The mobile app uses this information to establish a trust relationship with Azure AD B2C, sign users in and out, acquire tokens, and validate them. For more configuration settings, see the B2CConfiguration class.

Step 3: Configure your Android app

After you add the authentication components, configure your Android app with your Azure AD B2C settings. Azure AD B2C identity provider settings are configured in the auth_config_b2c.json file and B2CConfiguration class.

For guidance, see Configure the sample mobile app.

Step 4: Set the redirect URI

Configure where your application listens to the Azure AD B2C token response.

  1. Generate a new development signature hash. This will change for each development environment.

    For Windows:

    keytool -exportcert -alias androiddebugkey -keystore %HOMEPATH%\.android\debug.keystore | openssl sha1 -binary | openssl base64
    

    For iOS:

    keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
    

    For a production environment, use the following command:

    keytool -exportcert -alias SIGNATURE_ALIAS -keystore PATH_TO_KEYSTORE | openssl sha1 -binary | openssl base64
    

    For more help with signing your apps, see Sign your Android app.

  2. Select app > src > main > AndroidManifest.xml, and then add the following BrowserTabActivity activity to the application body:

    <!--Intent filter to capture System Browser or Authenticator calling back to our app after sign-in-->
    <activity
        android:name="com.microsoft.identity.client.BrowserTabActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="msauth"
                android:host="Package_Name"
                android:path="/Signature_Hash" />
        </intent-filter>
    </activity>
    
  3. Replace Signature_Hash with the hash you generated.

  4. Replace Package_Name with your Android package name.

To update the mobile app registration with your app redirect URI, do the following:

  1. Sign in to the Azure portal.
  2. If you have access to multiple tenants, select the Settings icon in the top menu to switch to your Azure AD B2C tenant from the Directories + subscriptions menu.
  3. Search for and select Azure AD B2C.
  4. Select App registrations, and then select the application you registered in Step 2.3: Register the mobile app.
  5. Select Authentication.
  6. Under Android, select Add URI.
  7. Enter the Package name and Signature hash.
  8. Select Save.

Your redirect URI and the BrowserTabActivity activity should look similar to the following sample:

The redirect URL for the sample Android looks like this:

msauth://com.azuresamples.msalandroidkotlinapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3D

The intent filter uses the same pattern, as shown in the following XML snippet:

<activity android:name="com.microsoft.identity.client.BrowserTabActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:host="com.azuresamples.msalandroidkotlinapp"
            android:path="/1wIqXSqBj7w+h11ZifsnqwgyKrY="
            android:scheme="msauth" />
    </intent-filter>
</activity>

Step 5: Customize your code building blocks

This section describes the code building blocks that enable authentication for your Android app. The following table lists the B2CModeFragment methods and how to customize your code.

Step 5.1: Instantiate a public client application

Public client applications aren't trusted to safely keep application secrets, and they don't have client secrets. In onCreate or onCreateView, instantiate the MSAL by using the multiple-account public client application object.

The MultipleAccountPublicClientApplication class is used to create MSAL-based apps that allow multiple accounts to be signed in at the same time. The class allows sign-in with multiple Azure AD B2C user flows or custom policies. For example, users sign in with a sign-up or sign-in user flow and later, they run an edit profile user flow.

The following code snippet demonstrates how to initiate the MSAL library with the auth_config_b2c.json configuration JSON file.

PublicClientApplication.createMultipleAccountPublicClientApplication(context!!,
    R.raw.auth_config_b2c,
    object : IMultipleAccountApplicationCreatedListener {
        override fun onCreated(application: IMultipleAccountPublicClientApplication) {
            // Set the MultipleAccountPublicClientApplication to the class member b2cApp
            b2cApp = application
            // Load the account (if there is any)
            loadAccounts()
        }

        override fun onError(exception: MsalException) {
            // Error handling
            displayError(exception)
        }
    })

Step 5.2: Load accounts

When the app comes to the foreground, the app loads the existing account to determine whether users are signed in. Use this method to update the UI with the authentication state. For example, you can enable or disable the sign-out button.

The following code snippet demonstrates how to load the accounts.

private fun loadAccounts() {
    if (b2cApp == null) {
        return
    }
    b2cApp!!.getAccounts(object : LoadAccountsCallback {
        override fun onTaskCompleted(result: List<IAccount>) {
            users = B2CUser.getB2CUsersFromAccountList(result)
            updateUI(users)
        }
    
        override fun onError(exception: MsalException) {
            displayError(exception)
        }
    })
    }

Step 5.3: Start an interactive authorization request

An interactive authorization request is a flow where users are prompted to sign up or sign in. The initializeUI method configures the runUserFlowButton click event. When users select the Run User Flow button, the app takes them to Azure AD B2C to complete the sign-in flow.

The runUserFlowButton.setOnClickListener method prepares the AcquireTokenParameters object with relevant data about the authorization request. The acquireToken method then prompts users to complete the sign-up or sign-in flow.

The following code snippet demonstrates how to start the interactive authorization request:

val parameters = AcquireTokenParameters.Builder()
        .startAuthorizationFromActivity(activity)
        .fromAuthority(getAuthorityFromPolicyName(policy_list.getSelectedItem().toString()))
        .withScopes(B2CConfiguration.scopes)
        .withPrompt(Prompt.LOGIN)
        .withCallback(authInteractiveCallback)
        .build()

b2cApp!!.acquireToken(parameters)

Step 5.4: Make an interactive authorization request callback

After users finish the authorization flow, whether successfully or unsuccessfully, the result is returned to the getAuthInteractiveCallback() callback method.

The callback method passes the AuthenticationResult object, or an error message in the MsalException object. Use this method to:

  • Update the mobile app UI with information after the sign-in has finished.
  • Reload the accounts object.
  • Call a web API service with an access token.
  • Handle authentication errors.

The following code snippet demonstrates the use of the interactive authentication callback.

private val authInteractiveCallback: AuthenticationCallback
    private get() = 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")

            /* display result info */
            displayResult(authenticationResult)

            /* Reload account asynchronously to get the up-to-date list. */
            loadAccounts()
        }

        override fun onError(exception: MsalException) {
            val B2C_PASSWORD_CHANGE = "AADB2C90118"
            if (exception.message!!.contains(B2C_PASSWORD_CHANGE)) {
                txt_log!!.text = """
                    Users click the 'Forgot Password' link in a sign-up or sign-in user flow.
                    Your application needs to handle this error code by running a specific user flow that resets the password.
                    """.trimIndent()
                return
            }

            /* Failed to acquireToken */Log.d(TAG, "Authentication failed: $exception")
            displayError(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.")
        }
    }

Step 6: Call a web API

To call a token-based authorization web API, the app needs to have a valid access token. The app does the following:

  1. Acquires an access token with the required permissions (scopes) for the web API endpoint.
  2. Passes the access token as a bearer token in the authorization header of the HTTP request by using this format:
Authorization: Bearer <access-token>

When users sign in interactively, the app gets an access token in the getAuthInteractiveCallback callback method. For consecutive web API calls, use the acquire token silent procedure as described in this section.

Before you call a web API, call the acquireTokenSilentAsync method with the appropriate scopes for your web API endpoint. The MSAL library does the following:

  1. Attempts to fetch an access token with the requested scopes from the token cache. If the token is present, the token is returned.
  2. If the token isn't present in the token cache, MSAL attempts to use its refresh token to acquire a new token.
  3. If the refresh token doesn't exist or has expired, an exception is returned. We recommend that you prompt the user to sign in interactively.

The following code snippet demonstrates how to acquire an access token:

The acquireTokenSilentButton button click event acquires an access token with the provided scopes.

btn_acquireTokenSilently.setOnClickListener(View.OnClickListener {
    if (b2cApp == null) {
        return@OnClickListener
    }
    val selectedUser = users!![user_list.getSelectedItemPosition()]
    selectedUser.acquireTokenSilentAsync(b2cApp!!,
            policy_list.getSelectedItem().toString(),
            B2CConfiguration.scopes,
            authSilentCallback)
})

The authSilentCallback callback method returns an access token and calls a web API:

private val authSilentCallback: SilentAuthenticationCallback
    private get() = object : SilentAuthenticationCallback {
        override fun onSuccess(authenticationResult: IAuthenticationResult) {
            Log.d(TAG, "Successfully authenticated")

            /* Call your web API here*/
            callWebAPI(authenticationResult)
        }

        override fun onError(exception: MsalException) {
            /* Failed to acquireToken */
            Log.d(TAG, "Authentication failed: $exception")
            displayError(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 */
            } else if (exception is MsalUiRequiredException) {
                /* Tokens expired or no session, retry with interactive */
            }
        }
    }

The following example demonstrates how to call a protected web API with a bearer token:

@Throws(java.lang.Exception::class)
private fun callWebAPI(authenticationResult: IAuthenticationResult) {
    val accessToken = authenticationResult.accessToken
    val thread = Thread {
        try {
            val url = URL("https://your-app-service.azurewebsites.net/helo")
            val conn = url.openConnection() as HttpsURLConnection
            conn.setRequestProperty("Accept", "application/json")
            
            // Set the bearer token
            conn.setRequestProperty("Authorization", "Bearer $accessToken")
            if (conn.responseCode == HttpURLConnection.HTTP_OK) {
                val br = BufferedReader(InputStreamReader(conn.inputStream))
                var strCurrentLine: String?
                while (br.readLine().also { strCurrentLine = it } != null) {
                    Log.d(TAG, strCurrentLine)
                }
            }
            conn.disconnect()
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    thread.start()
}

Add permission to perform network operations

To perform network operations in your application, add the following permission to your manifest. For more information, see Connect to the network.

<uses-permission android:name="android.permission.INTERNET"/>

Next steps

Learn how to: