チュートリアル:Android アプリケーションからユーザーをサインインさせて Microsoft Graph API を呼び出す
このチュートリアルでは、ユーザーのサインインを処理し、Microsoft Graph API を呼び出すアクセス トークンを取得するために Microsoft ID プラットフォームと連携する Android アプリを作成します。
このチュートリアルを完了すると、アプリケーションは、個人用の Microsoft アカウント (outlook.com、live.com など) と、Azure Active Directory を使用する会社や組織の職場または学校アカウントのサインインを受け入れるようになります。
このチュートリアルの内容:
- Android Studio で Android アプリ プロジェクトを作成する
- Azure portal でアプリを登録する
- ユーザーのサインインとサインアウトをサポートするコードを追加する
- Microsoft Graph API を呼び出すコードを追加する
- アプリケーションをテストする
前提条件
- Android Studio 3.5 以上
このチュートリアルのしくみ
このチュートリアルのアプリでは、ユーザーがサインインされ、ユーザーに代わってデータが取得されます。 このデータは、承認を要求し、Microsoft ID プラットフォームによって保護される API (Microsoft Graph API) を介してアクセスされます。
具体的には次のとおりです。
- お使いアプリで、ブラウザーまたは Microsoft Authenticator と Intune ポータル サイトを介してユーザーをサインインさせます。
- エンド ユーザーが、アプリケーションから要求されたアクセス許可を受け入れます。
- アプリケーションには、Microsoft Graph API 用のアクセス トークンが発行されます。
- アクセス トークンは、Web API への HTTP 要求に含められます。
- Microsoft Graph の応答を処理します。
このサンプルでは、Microsoft Authentication Library for Android (MSAL) の com.microsoft.identity.client を使用して認証を実装します。
MSAL により、トークンが自動的に更新され、デバイス上の他のアプリとの間のシングル サインオン (SSO) が提供されて、アカウントが管理されます。
このチュートリアルでは、Android 用の MSAL を使用する簡単な例を紹介します。 わかりやすくするため、単一アカウント モードのみを使用します。 さらに複雑なシナリオを試したい方は、GitHub で完成版のコード サンプルを参照してください。
プロジェクトの作成
Android アプリケーションがまだない場合は、次の手順に従って新しいプロジェクトを設定します。
- Android Studio を開き、 [Start a new Android Studio project](新しい Android Studio プロジェクトを開始する) を選択します。
- [Basic Activity](基本アクティビティ) を選択し、[Next](次へ) を選択します。
- アプリケーションに名前を付けます。
- パッケージ名を保存しておきます。 後ほど、Azure portal でそれを入力します。
- 言語を [Kotlin] から [Java] に変更します。
- [Minimum API level](最低 API レベル) を API 19 以上に設定し、[Finish](完了) をクリックします。
- プロジェクト ビューのドロップダウンで [Project](プロジェクト) を選択して、ソースとソース以外のプロジェクト ファイルを表示し、app/build.gradle を開いて、
targetSdkVersionを28に設定します。
Microsoft Authentication Library と統合する
アプリケーションの登録
Azure portal にサインインします。
複数のテナントにアクセスできる場合は、トップ メニューの [ディレクトリとサブスクリプション] フィルター
を使用して、アプリケーションを登録するテナントに切り替えます。Azure Active Directory を検索して選択します。
[管理] で [アプリの登録]>[新規登録] の順に選択します。
アプリケーションの [名前] を入力します。 この名前は、アプリのユーザーに表示される場合があります。また、後で変更することができます。
[登録] を選択します。
[管理] で、 [認証]>[プラットフォームの追加]>[Android] の順に選択します。
プロジェクトのパッケージ名を入力します。 コードをダウンロードした場合、この値は
com.azuresamples.msalandroidappです。[Android アプリの構成] ページの [署名ハッシュ] セクションで、[Generating a development Signature Hash](開発用署名ハッシュの生成) を選択し、ご自分のプラットフォームに使用する KeyTool コマンドをコピーします。
KeyTool.exe は、Java Development Kit (JDK) の一部としてインストールされます。 KeyTool コマンドを実行するには、OpenSSL ツールもインストールする必要があります。 詳細については、キーの生成に関する Android のドキュメントを参照してください。
KeyTool によって生成された署名ハッシュを入力します。
[構成] をクリックし、後でアプリを構成するときに入力できるように、 [Android の構成] ページに表示される [MSAL 構成] を保存しておきます。
[完了] を選択します。
アプリケーションの作成
Android Studio のプロジェクト ウィンドウで、app\src\main\res に移動します。
res を右クリックして、[New](新規)>[Directory](ディレクトリ) を選択します。 新しいディレクトリの名前に「
raw」と入力し、[OK] をクリックします。app>src>main>res>raw で、
auth_config_single_account.jsonという名前の新しい JSON ファイルを作成し、先ほど保存した MSAL 構成を貼り付けます。リダイレクト URI の下に、以下を貼り付けます。
"account_mode" : "SINGLE",構成ファイルは次の例のようになります。
{ "client_id" : "0984a7b6-bc13-4141-8b0d-8f767e136bb7", "authorization_user_agent" : "DEFAULT", "redirect_uri" : "msauth://com.azuresamples.msalandroidapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3D", "broker_redirect_uri_registered" : true, "account_mode" : "SINGLE", "authorities" : [ { "type": "AAD", "audience": { "type": "AzureADandPersonalMicrosoftAccount", "tenant_id": "common" } } ] }このチュートリアルでは、単一アカウント モードでアプリを構成する方法についてのみ説明します。 単一アカウント モードと複数アカウント モードの比較およびアプリの構成の詳細については、ドキュメントを参照してください
app>src>main>AndroidManifest.xml で、以下の
BrowserTabActivityアクティビティをアプリケーション本文に追加します。 このエントリにより、Microsoft は認証の完了後にアプリケーションにコールバックできます。<!--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="Enter_the_Package_Name" android:path="/Enter_the_Signature_Hash" /> </intent-filter> </activity>android:host=は、Azure portal で登録したパッケージ名に置き換えます。android:path=は、Azure portal で登録したキー ハッシュに置き換えます。 署名ハッシュは、URL にエンコードしてはいけません。 署名ハッシュの先頭に/があることを確認します。android:host値を置き換える "パッケージ名" は、com.azuresamples.msalandroidappのようになります。android:path値を置き換える "署名ハッシュ" は、/1wIqXSqBj7w+h11ZifsnqwgyKrY=のようになります。これらの値は、アプリの登録の [認証] ブレードでも確認できます。 リダイレクト URI は、
msauth://com.azuresamples.msalandroidapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3Dのようになります。 この値では署名ハッシュが URL の最後にエンコードされていますが、android:pathの値では署名ハッシュを URL にエンコードしてはいけません。
MSAL の使用
プロジェクトに MSAL を追加する
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.+' implementation 'com.microsoft.graph:microsoft-graph:1.5.+' } packagingOptions{ exclude("META-INF/jersey-module-version") }
必要なインポート
app>src>main>java>com.example(yourapp)>MainActivity.java の先頭に、以下を追加します
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.gson.JsonObject;
import com.microsoft.graph.authentication.IAuthenticationProvider; //Imports the Graph sdk Auth interface
import com.microsoft.graph.concurrency.ICallback;
import com.microsoft.graph.core.ClientException;
import com.microsoft.graph.http.IHttpRequest;
import com.microsoft.graph.models.extensions.*;
import com.microsoft.graph.requests.extensions.GraphServiceClient;
import com.microsoft.identity.client.AuthenticationCallback; // Imports MSAL auth methods
import com.microsoft.identity.client.*;
import com.microsoft.identity.client.exception.*;
PublicClientApplication をインスタンス化する
変数を初期化する
private final static String[] SCOPES = {"Files.Read"};
/* Azure AD v2 Configs */
final static String AUTHORITY = "https://login.microsoftonline.com/common";
private ISingleAccountPublicClientApplication mSingleAccountApp;
private static final String TAG = MainActivity.class.getSimpleName();
/* UI & Debugging Variables */
Button signInButton;
Button signOutButton;
Button callGraphApiInteractiveButton;
Button callGraphApiSilentButton;
TextView logTextView;
TextView currentUserTextView;
onCreate
MainActivity クラス内で、次の onCreate() メソッドを参照し、SingleAccountPublicClientApplication を使用して MSAL をインスタンス化します。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeUI();
PublicClientApplication.createSingleAccountPublicClientApplication(getApplicationContext(),
R.raw.auth_config_single_account, new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
@Override
public void onCreated(ISingleAccountPublicClientApplication application) {
mSingleAccountApp = application;
loadAccount();
}
@Override
public void onError(MsalException exception) {
displayError(exception);
}
});
}
loadAccount
//When app comes to the foreground, load existing account to determine if user is signed in
private void loadAccount() {
if (mSingleAccountApp == null) {
return;
}
mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() {
@Override
public void onAccountLoaded(@Nullable IAccount activeAccount) {
// You can use the account data to update your UI or your app database.
updateUI(activeAccount);
}
@Override
public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) {
if (currentAccount == null) {
// Perform a cleanup task as the signed-in account changed.
performOperationOnSignOut();
}
}
@Override
public void onError(@NonNull MsalException exception) {
displayError(exception);
}
});
}
initializeUI
ボタンをリッスンし、それに応じて、メソッドを呼び出すか、エラーをログに記録します。
private void initializeUI(){
signInButton = findViewById(R.id.signIn);
callGraphApiSilentButton = findViewById(R.id.callGraphSilent);
callGraphApiInteractiveButton = findViewById(R.id.callGraphInteractive);
signOutButton = findViewById(R.id.clearCache);
logTextView = findViewById(R.id.txt_log);
currentUserTextView = findViewById(R.id.current_user);
//Sign in user
signInButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
if (mSingleAccountApp == null) {
return;
}
mSingleAccountApp.signIn(MainActivity.this, null, SCOPES, getAuthInteractiveCallback());
}
});
//Sign out user
signOutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSingleAccountApp == null){
return;
}
mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() {
@Override
public void onSignOut() {
updateUI(null);
performOperationOnSignOut();
}
@Override
public void onError(@NonNull MsalException exception){
displayError(exception);
}
});
}
});
//Interactive
callGraphApiInteractiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSingleAccountApp == null) {
return;
}
mSingleAccountApp.acquireToken(MainActivity.this, SCOPES, getAuthInteractiveCallback());
}
});
//Silent
callGraphApiSilentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSingleAccountApp == null){
return;
}
mSingleAccountApp.acquireTokenSilentAsync(SCOPES, AUTHORITY, getAuthSilentCallback());
}
});
}
重要
MSAL でサインアウトすると、ユーザーに関する既知の情報がすべてこのアプリケーションから削除されますが、ユーザーのデバイスではアクティブなセッションが維持されます。 ユーザーが再びサインインを試みた場合、サインイン UI が表示されることがありますが、デバイス セッションはまだアクティブであるため、資格情報を再入力する必要はありません。
getAuthInteractiveCallback
対話型要求に使用されるコールバック。
private AuthenticationCallback getAuthInteractiveCallback() {
return new AuthenticationCallback() {
@Override
public void onSuccess(IAuthenticationResult authenticationResult) {
/* Successfully got a token, use it to call a protected resource - MSGraph */
Log.d(TAG, "Successfully authenticated");
/* Update UI */
updateUI(authenticationResult.getAccount());
/* call graph */
callGraphAPI(authenticationResult);
}
@Override
public void onError(MsalException exception) {
/* Failed to acquireToken */
Log.d(TAG, "Authentication failed: " + exception.toString());
displayError(exception);
}
@Override
public void onCancel() {
/* User canceled the authentication */
Log.d(TAG, "User cancelled login.");
}
};
}
getAuthSilentCallback
サイレント要求に使用されるコールバック
private SilentAuthenticationCallback getAuthSilentCallback() {
return new SilentAuthenticationCallback() {
@Override
public void onSuccess(IAuthenticationResult authenticationResult) {
Log.d(TAG, "Successfully authenticated");
callGraphAPI(authenticationResult);
}
@Override
public void onError(MsalException exception) {
Log.d(TAG, "Authentication failed: " + exception.toString());
displayError(exception);
}
};
}
Microsoft Graph API を呼び出す
次のコードでは、Graph SDK を使用して GraphAPI を呼び出す方法を示します。
callGraphAPI
private void callGraphAPI(IAuthenticationResult authenticationResult) {
final String accessToken = authenticationResult.getAccessToken();
IGraphServiceClient graphClient =
GraphServiceClient
.builder()
.authenticationProvider(new IAuthenticationProvider() {
@Override
public void authenticateRequest(IHttpRequest request) {
Log.d(TAG, "Authenticating request," + request.getRequestUrl());
request.addHeader("Authorization", "Bearer " + accessToken);
}
})
.buildClient();
graphClient
.me()
.drive()
.buildRequest()
.get(new ICallback<Drive>() {
@Override
public void success(final Drive drive) {
Log.d(TAG, "Found Drive " + drive.id);
displayGraphResult(drive.getRawObject());
}
@Override
public void failure(ClientException ex) {
displayError(ex);
}
});
}
UI の追加
アクティビティ
このチュートリアルで UI をモデル化する場合は、次のメソッドを使用して、テキストの更新とボタンのリッスンを行うことができます。
updateUI
サインインの状態に基づいてボタンを有効または無効にし、テキストを設定します。
private void updateUI(@Nullable final IAccount account) {
if (account != null) {
signInButton.setEnabled(false);
signOutButton.setEnabled(true);
callGraphApiInteractiveButton.setEnabled(true);
callGraphApiSilentButton.setEnabled(true);
currentUserTextView.setText(account.getUsername());
} else {
signInButton.setEnabled(true);
signOutButton.setEnabled(false);
callGraphApiInteractiveButton.setEnabled(false);
callGraphApiSilentButton.setEnabled(false);
currentUserTextView.setText("");
logTextView.setText("");
}
}
displayError
private void displayError(@NonNull final Exception exception) {
logTextView.setText(exception.toString());
}
displayGraphResult
private void displayGraphResult(@NonNull final JsonObject graphResponse) {
logTextView.setText(graphResponse.toString());
}
performOperationOnSignOut
サインアウトを反映して UI 内のテキストを更新するメソッド。
private void performOperationOnSignOut() {
final String signOutText = "Signed Out.";
currentUserTextView.setText("");
Toast.makeText(getApplicationContext(), signOutText, Toast.LENGTH_SHORT)
.show();
}
レイアウト
ボタンとテキスト ボックスを表示するサンプルの activity_main.xml ファイル。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:weightSum="10">
<Button
android:id="@+id/signIn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:gravity="center"
android:text="Sign In"/>
<Button
android:id="@+id/clearCache"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:gravity="center"
android:text="Sign Out"
android:enabled="false"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/callGraphInteractive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:text="Get Graph Data Interactively"
android:enabled="false"/>
<Button
android:id="@+id/callGraphSilent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:text="Get Graph Data Silently"
android:enabled="false"/>
</LinearLayout>
<TextView
android:text="Getting Graph Data..."
android:textColor="#3f3f3f"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:id="@+id/graphData"
android:visibility="invisible"/>
<TextView
android:id="@+id/current_user"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="20dp"
android:layout_weight="0.8"
android:text="Account info goes here..." />
<TextView
android:id="@+id/txt_log"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="20dp"
android:layout_weight="0.8"
android:text="Output goes here..." />
</LinearLayout>
アプリをテストする
ローカルで実行する
アプリをビルドし、テスト デバイスまたはエミュレーターに展開します。 サインインして、Azure AD または個人用 Microsoft アカウントのトークンを取得できるようになります。
サインインした後、Microsoft Graph の /me エンドポイントから返されたデータがアプリに表示されます。
PR 4
同意
ユーザーは、アプリに初めてサインインするときに、Microsoft Identity から、要求されたアクセス許可に同意するよう求められます。 一部の Azure AD テナントではユーザーによる同意が無効になっており、全ユーザーに代わって管理者が同意を行う必要があります。 このシナリオをサポートするには、独自のテナントを作成するか、管理者の同意を受け取る必要があります。
リソースをクリーンアップする
必要がなくなったら、「アプリケーションの登録」の手順で作成したアプリ オブジェクトを削除します。
ヘルプとサポート
サポートが必要な場合、問題をレポートする場合、またはサポート オプションについて知りたい場合は、開発者向けのヘルプとサポートに関するページを参照してください。
次のステップ
保護された Web API を呼び出すモバイル アプリを作成するには、複数のパートで構成される次のシナリオ シリーズを参照してください。