Azure AD B2C を使用して独自の Android アプリで認証を有効にする

この記事では、独自の Android モバイル アプリケーションに Azure Active Directory B2C (Azure AD B2C) 認証を追加する方法について説明します。

この記事は、「Azure Active Directory B2C を使用してサンプル Android アプリケーションで認証を構成する」に関する記事と共に使用してください。その際は、サンプル Android アプリを独自の Android アプリに置き換えてください。 この記事の手順を完了すると、アプリケーションで、Azure AD B2C を使用したサインインが受け付けられるようになります。

前提条件

Azure Active Directory B2C を使用してサンプル Android アプリケーションで認証を構成する」の前提条件と統合の手順を確認します。

Android アプリ プロジェクトを作成する

Android アプリケーションがまだない場合は、以下を実行して新しいプロジェクトを設定します。

  1. Android Studio で、 [Start a new Android Studio project](新しい Android Studio プロジェクトを開始する) を選択します。
  2. [Basic Activity](基本アクティビティ) を選択し、 [Next](次へ) を選択します。
  3. アプリケーションに名前を付けます。
  4. パッケージ名を保存しておきます。 後ほど、Azure portal でそれを入力します。
  5. 言語を [Kotlin] から [Java] に変更します。
  6. [最低 API レベル]API 19 以上に設定し、 [完了] を選択します。
  7. プロジェクト ビューのドロップダウン リストで [Project](プロジェクト) を選択して、ソースとソース以外のプロジェクト ファイルを表示し、app/build.gradle を開いて、targetSdkVersion28 に設定します。

ステップ 1: 依存関係をインストールする

Android Studio のプロジェクト ウィンドウで、app>build.gradle に移動し、以下を追加します。

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")
}

ステップ 2: 認証コンポーネントを追加する

サンプル コードは、次のコンポーネントで構成されています。 サンプル Android アプリから独自のアプリにこれらのコンポーネントを追加します。

コンポーネント Type source 説明
B2CUser クラス KotlinJava B2C ユーザーを表します。 このクラスを使用すると、ユーザーは複数のポリシーを使用してサインインできます。
B2CModeFragment Fragment クラス KotlinJava フラグメントは、メイン アクティビティ内の Azure AD B2C ユーザー インターフェイスを使用したサインインのモジュール部分を表します。 認証コードの大半は、このフラグメントに含まれています。
fragment_b2c_mode.xml フラグメント レイアウト KotlinJava B2CModeFragment フラグメント コンポーネントのユーザー インターフェイスの構造を定義します。
B2CConfiguration クラス KotlinJava Azure AD B2C ID プロバイダーに関する情報は、構成ファイルに含まれています。 モバイル アプリでは、この情報を使用して Azure AD B2C との信頼関係を確立し、ユーザーのサインインとサインアウトを行い、トークンを取得して検証します。 その他の構成設定については、auth_config_b2c.json ファイルを参照してください。
auth_config_b2c.json JSON ファイル KotlinJava Azure AD B2C ID プロバイダーに関する情報は、構成ファイルに含まれています。 モバイル アプリでは、この情報を使用して Azure AD B2C との信頼関係を確立し、ユーザーのサインインとサインアウトを行い、トークンを取得して検証します。 その他の構成設定については、B2CConfiguration クラスを参照してください。

ステップ 3: Android アプリを構成する

認証コンポーネントを追加したら、Azure AD B2C 設定を使用して Android アプリを構成します。 Azure AD B2C ID プロバイダー設定の構成は、auth_config_b2c.json ファイルと B2CConfiguration クラスで行います。

ガイダンスについては、「サンプル モバイル アプリを構成する」を参照してください。

ステップ 4: リダイレクト URI を設定する

アプリケーションで Azure AD B2C のトークン応答をリッスンする場所を構成します。

  1. 新しい開発署名ハッシュを生成します。 これは開発環境ごとに変わります。

    Windows の場合:

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

    iOS の場合:

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

    運用環境では、次のコマンドを使用します。

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

    アプリの署名について詳しくは、Android アプリの署名に関するページを参照してください。

  2. app>src>main>AndroidManifest.xml を選択し、以下の BrowserTabActivity アクティビティをアプリケーションの本体に追加します。

    <!--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. Signature_Hash を、自分が生成したハッシュに置き換えます。

  4. Package_Name を、自分の Android パッケージの名前に置き換えます。

モバイル アプリの登録をアプリのリダイレクト URI で更新するには、次のようにします。

  1. Azure portal にサインインします。
  2. 複数のテナントにアクセスできる場合、上部のメニューの [設定] アイコンを選択し、[ディレクトリとサブスクリプション] メニューからお使いの Azure AD B2C テナントに切り替えます。
  3. Azure AD B2C を検索して選択します。
  4. [アプリの登録] を選択し、「手順 2.3 モバイル アプリを登録する」で登録したアプリケーションを選択します。
  5. [認証] を選択します。
  6. [Android][URI の追加] を選択します。
  7. [パッケージ名][署名ハッシュ] を入力します。
  8. [保存] を選択します。

リダイレクト URI と BrowserTabActivity アクティビティは、次の例のようになっている必要があります。

サンプル Android のリダイレクト URL は次のようになります。

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

次の XML スニペットで示されているように、インテント フィルターにも同じパターンが使用されます。

<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>

ステップ 5: コードの構成要素をカスタマイズする

このセクションでは、Android アプリの認証を可能にするコードの構成要素について説明します。 次の表は、B2CModeFragment のメソッドと、コードのカスタマイズ方法の一覧です。

ステップ 5.1: パブリック クライアント アプリケーションをインスタンス化する

パブリック クライアント アプリケーションは、アプリケーション シークレットが安全に保管されると信頼されておらず、そこにクライアント シークレットは置かれません。 onCreate または onCreateView で、MultipleAccountPublicClientApplication オブジェクトを使用して MSAL のインスタンスを作成します。

MultipleAccountPublicClientApplication クラスは、複数のアカウントが同時にサインインできるようにする MSAL ベースのアプリを作成するために使用されます。 このクラスにより、複数の Azure AD B2C ユーザー フローまたはカスタム ポリシーを使用したサインインが可能となります。 たとえば、ユーザーは、サインアップまたはサインイン ユーザー フローを使用してサインインし、その後、プロファイル編集ユーザー フローを実行します。

次のコード スニペットは、auth_config_b2c.json 構成 JSON ファイルを使用して MSAL ライブラリを初期化する方法を示しています。

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)
        }
    })

ステップ 5.2: アカウントを読み込む

アプリはフォアグラウンドになると、既存のアカウントを読み込んで、ユーザーがサインイン済みかどうかを判断します。 この方法を使用して UI を認証状態で更新します。 たとえば、サインアウト ボタンを有効または無効にできます。

アカウントを読み込む方法を次のコード スニペットに示します。

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)
        }
    })
    }

ステップ 5.3: 対話型承認要求を開始する

対話型承認要求は、ユーザーがサインアップまたはサインインを求められるフローです。 runUserFlowButton クリック イベントの構成は、initializeUI メソッドで行います。 ユーザーが [ユーザー フローを実行します] ボタンを選択すると、ユーザーはアプリによって Azure AD B2C に誘導され、サインイン フローが実行されます。

runUserFlowButton.setOnClickListener メソッドにより、承認要求に関する関連データを使用して AcquireTokenParameters オブジェクトが準備されます。 その後、ユーザーはサインアップ フローまたはサインイン フローを実行するよう、acquireToken メソッドによって求められます。

次のコード スニペットは、対話型承認要求の開始方法を示しています。

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

b2cApp!!.acquireToken(parameters)

ステップ 5.4: 対話型承認要求のコールバックを行う

ユーザーが成功または失敗で承認フローを完了した後、getAuthInteractiveCallback() コールバック メソッドに結果が返されます。

このコールバック メソッドによって AuthenticationResult オブジェクトが渡されるか、MsalException オブジェクトにエラー メッセージが渡されます。 このメソッドを使用して以下を実行します。

  • サインイン完了後の情報でモバイル アプリの UI を更新します。
  • アカウント オブジェクトを再度読み込みます。
  • アクセス トークンを使用して Web API サービスを呼び出します。
  • 認証エラーを処理します。

次のコード スニペットは、対話型認証コールバックの使用例を示しています。

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.")
        }
    }

ステップ 6: Web API を呼び出す

トークンベースの承認 Web API を呼び出すには、有効なアクセス トークンがアプリにある必要があります。 アプリでは以下が実行されます。

  1. Web API エンドポイントで必要なアクセス許可 (スコープ) を持つアクセス トークンを取得します。
  2. この形式を使用して、HTTP 要求の Authorization ヘッダーでベアラー トークンとしてアクセス トークンを渡します。
Authorization: Bearer <access-token>

ユーザーが対話形式でサインインすると、アプリは getAuthInteractiveCallback コールバック メソッド内でアクセス トークンを取得します。 連続する Web API 呼び出しの場合は、このセクションで説明するように、トークンをサイレントに取得するプロシージャを使用します。

Web API を呼び出す前に、Web API エンドポイントに対する適切なスコープで acquireTokenSilentAsync メソッドを呼び出します。 MSAL ライブラリによって以下のことが行われます。

  1. 要求されたスコープを持つアクセス トークンをトークン キャッシュから取り込むことを試みます。 トークンが存在する場合は、そのトークンが返されます。
  2. トークンがトークン キャッシュに存在しない場合、MSAL は更新トークンを使用して新しいトークンを取得しようと試みます。
  3. 更新トークンが存在しないか期限切れになっている場合は、例外が返されます。 対話形式でのサインインをユーザーに求めることをお勧めします。

次のコード スニペットは、アクセス トークンの取得方法を示しています。

acquireTokenSilentButton ボタン クリック イベントは、指定されたスコープのアクセス トークンを取得します。

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)
})

authSilentCallback コールバック メソッドは、アクセス トークンを返して 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 */
            }
        }
    }

次の例は、保護された Web API をベアラー トークンで呼び出す方法を示しています。

@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()
}

ネットワーク操作を実行するためのアクセス許可を追加する

アプリケーションでネットワーク操作を実行するには、マニフェストに次のアクセス許可を追加する必要があります。 詳細については、「ネットワークに接続する」を参照してください。

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

次のステップ

具体的には、次の方法を学習します。