Tutoriel : Connecter des utilisateurs et appeler l’API Microsoft Graph à partir d’une application Android
Dans ce tutoriel, vous allez créer une application Android qui s’intègre à la plateforme Microsoft Entra ID pour connecter des utilisateurs et obtenir un jeton d’accès pour appeler l’API Microsoft Graph.
À la fin de ce tutoriel, votre application accepte les connexions de comptes Microsoft personnels (y compris outlook.com, live.com et d’autres) et de comptes professionnels ou scolaires de n’importe quelle entreprise ou organisation utilisant Microsoft Entra ID.
Dans ce tutoriel :
- Créer un projet d’application Android dans Android Studio
- Inscrire l’application dans le centre d’administration Microsoft Entra
- Ajouter du code pour prendre en charge la connexion et la déconnexion des utilisateurs
- Ajouter du code pour appeler l’API Microsoft Graph.
- Test de l'application
Prérequis
Fonctionnement de ce tutoriel
L’application utilisée dans ce tutoriel est destinée à connecter des utilisateurs et à obtenir des données au nom de ces derniers. Ces données sont accessibles par le biais d’une API protégée (l’API Microsoft Graph) qui nécessite une autorisation et est protégée par la plateforme d’identités Microsoft.
Cet exemple utilise la bibliothèque d’authentification Microsoft (MSAL) pour Android pour implémenter l’authentification : com.microsoft.identity.client.
Création d’un projet
Procédez comme suit pour configurer un nouveau projet si vous n’avez pas encore d’application Android.
- Ouvrez Android Studio, puis sélectionnez Démarrer un nouveau projet Android Studio.
- Sélectionnez Activité de base, puis Suivant.
- Entrez un nom pour l’application, par exemple MSALAndroidapp.
- Enregistrez le nom du package à utiliser dans les étapes ultérieures.
- Remplacez le langage Kotlin par Java.
- Définissez le Niveau d’API SDK minimal sur API 16 ou plus, puis sélectionnez Terminer.
Inscrivez votre application avec un compte Microsoft Entra ID
Conseil
Les étapes décrites dans cet article peuvent varier légèrement en fonction du portail de départ.
Connectez-vous au centre d’administration de Microsoft Entra au minimum en tant que Développeur d’application.
Si vous avez accès à plusieurs tenants, utilisez l’icône Paramètres dans le menu supérieur pour basculer vers le tenant dans lequel vous voulez inscrire l’application à partir du menu Répertoires + abonnements.
Accédez à Identité>Applications>Inscriptions d’applications.
Sélectionnez Nouvelle inscription.
Entrez un nom pour votre application. Les utilisateurs de votre application peuvent voir ce nom, et vous pouvez le changer ultérieurement.
Sous Types de comptes pris en charge, sélectionnez Comptes dans un annuaire d’organisation (tout annuaire Microsoft Entra - Multilocataire) et comptes Microsoft personnels (par exemple, Skype, Xbox). Pour plus d’informations sur les différents types de comptes, sélectionnez l’option M’aider à choisir.
Sélectionnez Inscription.
Sous Gérer, sélectionnez Authentification>Ajouter une plateforme>Android.
Entrez le nom du package de votre projet. Si vous avez téléchargé l’exemple de code, cette valeur est
com.azuresamples.msalandroidapp
.Dans la section Hachage de signature de la page Configurer votre application Android, sélectionnez Création d’un hachage de signature de développement, puis copiez la commande KeyTool vers votre ligne de commande.
- Keytool.exe est installé en même temps que le kit de développement Java (JDK). Vous devez également installer l’outil OpenSSL pour exécuter la commande KeyTool. Pour plus d’informations, consultez la documentation Android sur la génération d’une clé.
Entrez le Hachage de signature généré par KeyTool.
Sélectionnez Configurer et enregistrez la Configuration MSAL qui apparaît dans la page Configuration Android pour pouvoir l’entrer plus tard quand vous devrez configurer votre application.
Sélectionnez Terminé.
Configuration de votre application
Dans le volet de projet d’Android Studio, accédez à app\src\main\res.
Cliquez avec le bouton droit sur res, puis choisissez Nouveau>Répertoire. Entrez
raw
en tant que nouveau nom de répertoire, puis sélectionnez OK.Dans app>src>main>res>raw, créez un fichier JSON appelé
auth_config_single_account.json
, puis collez la configuration MSAL que vous avez enregistrée.Sous l’URI de redirection, collez :
"account_mode" : "SINGLE",
Votre fichier de configuration doit ressembler à l’exemple suivant :
{ "client_id": "00001111-aaaa-bbbb-3333-cccc4444", "authorization_user_agent": "WEBVIEW", "redirect_uri": "msauth://com.azuresamples.msalandroidapp/00001111%cccc4444%3D", "broker_redirect_uri_registered": true, "account_mode": "SINGLE", "authorities": [ { "type": "AAD", "audience": { "type": "AzureADandPersonalMicrosoftAccount", "tenant_id": "common" } } ] }
Comme ce tutoriel montre uniquement comment configurer une application en mode compte unique, consultez Le mode compte unique ou multiple et la configuration de votre application pour plus d’informations
Nous vous recommandons d’utiliser « WEBVIEW ». Si vous souhaitez configurer « authorization_user_agent » comme « BROWSER » dans votre application, vous devez effectuer les mises à jour suivantes. a) Mettez à jour auth_config_single_account.json avec "authorization_user_agent": "Browser". b) Mettez à jour AndroidManifest.xml. Dans l’application, accédez à app>src>main>AndroidManifest.xml, ajoutez l’activité
BrowserTabActivity
en tant qu’enfant de l’élément<application>
. Cette entrée permet à Microsoft Entra ID de rappeler votre application une fois l’authentification terminée :<!--Intent filter to capture System Browser or Authenticator calling back to our app after sign-in--> <activity android:name="com.microsoft.identity.client.BrowserTabActivity" android:exported="true"> <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>
- Utilisez le Nom du package pour remplacer la valeur
android:host=.
. Il doit ressembler àcom.azuresamples.msalandroidapp
. - Utilisez le Hachage de signature pour remplacer la valeur
android:path=
. Assurez-vous de la présence d’un caractère/
de début dans votre hachage de signature. Il doit ressembler à/aB1cD2eF3gH4+iJ5kL6-mN7oP8q=
.
Vous pouvez également trouver ces valeurs dans le panneau Authentification de l’inscription de votre application.
- Utilisez le Nom du package pour remplacer la valeur
Ajouter MSAL et les bibliothèques pertinentes à votre projet
Dans la fenêtre de projet Android Studio, accédez à app>build.gradle et ajoutez les bibliothèques suivantes dans la section dépendances :
implementation 'com.microsoft.identity.client:msal:5.0.0' implementation 'com.android.volley:volley:1.2.1'
Dans la fenêtre de projet Android Studio, ouvrez settings.gradle et déclarez le dépôt maven suivant dans la section dependencyResolutionManagement>repositories :
maven { url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1' }
Sélectionnez Synchroniser maintenant dans la barre de notification.
Créer et mettre à jour le fragment requis
Dans app>src>main>java>com.example(nom de votre application). Créez les fragments Android suivants :
- MSGraphRequestWrapper
- OnFragmentInteractionListener
- SingleAccountModeFragment
Ouvrez MSGraphRequestWrapper.java et remplacez le code par l’extrait de code suivant pour appeler l’API Graph Microsoft à l’aide du jeton fourni par MSAL :
package com.azuresamples.msalandroidapp; import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; import com.android.volley.DefaultRetryPolicy; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.Volley; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; public class MSGraphRequestWrapper { private static final String TAG = MSGraphRequestWrapper.class.getSimpleName(); // See: https://docs.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints public static final String MS_GRAPH_ROOT_ENDPOINT = "https://graph.microsoft.com/"; /** * Use Volley to make an HTTP request with * 1) a given MSGraph resource URL * 2) an access token * to obtain MSGraph data. **/ public static void callGraphAPIUsingVolley(@NonNull final Context context, @NonNull final String graphResourceUrl, @NonNull final String accessToken, @NonNull final Response.Listener<JSONObject> responseListener, @NonNull final Response.ErrorListener errorListener) { Log.d(TAG, "Starting volley request to graph"); /* Make sure we have a token to send to graph */ if (accessToken == null || accessToken.length() == 0) { return; } RequestQueue queue = Volley.newRequestQueue(context); JSONObject parameters = new JSONObject(); try { parameters.put("key", "value"); } catch (Exception e) { Log.d(TAG, "Failed to put parameters: " + e.toString()); } JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, graphResourceUrl, parameters, responseListener, errorListener) { @Override public Map<String, String> getHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("Authorization", "Bearer " + accessToken); return headers; } }; Log.d(TAG, "Adding HTTP GET to Queue, Request: " + request.toString()); request.setRetryPolicy(new DefaultRetryPolicy( 3000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); queue.add(request); } }
Ouvrez OnFragmentInteractionListener.java et remplacez le code par l’extrait de code suivant pour permettre la communication entre différents fragments :
package com.azuresamples.msalandroidapp; /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated * to the activity and potentially other fragments contained in that * activity. * <p> * See the Android Training lesson <a href= * "http://developer.android.com/training/basics/fragments/communicating.html" * >Communicating with Other Fragments</a> for more information. */ public interface OnFragmentInteractionListener { }
Ouvrez SingleAccountModeFragment.java et remplacez le code par l’extrait de code suivant pour initialiser une application à compte unique, chargez un compte d’utilisateur et obtient un jeton pour appeler Microsoft API Graph :
package com.azuresamples.msalandroidapp; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.android.volley.Response; import com.android.volley.VolleyError; import com.microsoft.identity.client.AuthenticationCallback; import com.microsoft.identity.client.IAccount; import com.microsoft.identity.client.IAuthenticationResult; import com.microsoft.identity.client.IPublicClientApplication; import com.microsoft.identity.client.ISingleAccountPublicClientApplication; import com.microsoft.identity.client.PublicClientApplication; import com.microsoft.identity.client.SilentAuthenticationCallback; import com.microsoft.identity.client.exception.MsalClientException; import com.microsoft.identity.client.exception.MsalException; import com.microsoft.identity.client.exception.MsalServiceException; import com.microsoft.identity.client.exception.MsalUiRequiredException; import org.json.JSONObject; /** * Implementation sample for 'Single account' mode. * <p> * If your app only supports one account being signed-in at a time, this is for you. * This requires "account_mode" to be set as "SINGLE" in the configuration file. * (Please see res/raw/auth_config_single_account.json for more info). * <p> * Please note that switching mode (between 'single' and 'multiple' might cause a loss of data. */ public class SingleAccountModeFragment extends Fragment { private static final String TAG = SingleAccountModeFragment.class.getSimpleName(); /* UI & Debugging Variables */ Button signInButton; Button signOutButton; Button callGraphApiInteractiveButton; Button callGraphApiSilentButton; TextView scopeTextView; TextView graphResourceTextView; TextView logTextView; TextView currentUserTextView; TextView deviceModeTextView; /* Azure AD Variables */ private ISingleAccountPublicClientApplication mSingleAccountApp; private IAccount mAccount; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment final View view = inflater.inflate(R.layout.fragment_single_account_mode, container, false); initializeUI(view); // Creates a PublicClientApplication object with res/raw/auth_config_single_account.json PublicClientApplication.createSingleAccountPublicClientApplication(getContext(), R.raw.auth_config_single_account, new IPublicClientApplication.ISingleAccountApplicationCreatedListener() { @Override public void onCreated(ISingleAccountPublicClientApplication application) { /** * This test app assumes that the app is only going to support one account. * This requires "account_mode" : "SINGLE" in the config json file. **/ mSingleAccountApp = application; loadAccount(); } @Override public void onError(MsalException exception) { displayError(exception); } }); return view; } /** * Initializes UI variables and callbacks. */ private void initializeUI(@NonNull final View view) { signInButton = view.findViewById(R.id.btn_signIn); signOutButton = view.findViewById(R.id.btn_removeAccount); callGraphApiInteractiveButton = view.findViewById(R.id.btn_callGraphInteractively); callGraphApiSilentButton = view.findViewById(R.id.btn_callGraphSilently); scopeTextView = view.findViewById(R.id.scope); graphResourceTextView = view.findViewById(R.id.msgraph_url); logTextView = view.findViewById(R.id.txt_log); currentUserTextView = view.findViewById(R.id.current_user); deviceModeTextView = view.findViewById(R.id.device_mode); final String defaultGraphResourceUrl = MSGraphRequestWrapper.MS_GRAPH_ROOT_ENDPOINT + "v1.0/me"; graphResourceTextView.setText(defaultGraphResourceUrl); signInButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mSingleAccountApp == null) { return; } mSingleAccountApp.signIn(getActivity(), null, getScopes(), getAuthInteractiveCallback()); } }); signOutButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mSingleAccountApp == null) { return; } /** * Removes the signed-in account and cached tokens from this app (or device, if the device is in shared mode). */ mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() { @Override public void onSignOut() { mAccount = null; updateUI(); showToastOnSignOut(); } @Override public void onError(@NonNull MsalException exception) { displayError(exception); } }); } }); callGraphApiInteractiveButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mSingleAccountApp == null) { return; } /** * If acquireTokenSilent() returns an error that requires an interaction (MsalUiRequiredException), * invoke acquireToken() to have the user resolve the interrupt interactively. * * Some example scenarios are * - password change * - the resource you're acquiring a token for has a stricter set of requirement than your Single Sign-On refresh token. * - you're introducing a new scope which the user has never consented for. */ mSingleAccountApp.acquireToken(getActivity(), getScopes(), getAuthInteractiveCallback()); } }); callGraphApiSilentButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mSingleAccountApp == null) { return; } /** * Once you've signed the user in, * you can perform acquireTokenSilent to obtain resources without interrupting the user. */ mSingleAccountApp.acquireTokenSilentAsync(getScopes(), mAccount.getAuthority(), getAuthSilentCallback()); } }); } @Override public void onResume() { super.onResume(); /** * The account may have been removed from the device (if broker is in use). * * In shared device mode, the account might be signed in/out by other apps while this app is not in focus. * Therefore, we want to update the account state by invoking loadAccount() here. */ loadAccount(); } /** * Extracts a scope array from a text field, * i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"] */ private String[] getScopes() { return scopeTextView.getText().toString().toLowerCase().split(" "); } /** * Load the currently signed-in account, if there's any. */ 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. mAccount = activeAccount; updateUI(); } @Override public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) { if (currentAccount == null) { // Perform a cleanup task as the signed-in account changed. showToastOnSignOut(); } } @Override public void onError(@NonNull MsalException exception) { displayError(exception); } }); } /** * Callback used in for silent acquireToken calls. */ private SilentAuthenticationCallback getAuthSilentCallback() { return new SilentAuthenticationCallback() { @Override public void onSuccess(IAuthenticationResult authenticationResult) { Log.d(TAG, "Successfully authenticated"); /* Successfully got a token, use it to call a protected resource - MSGraph */ callGraphAPI(authenticationResult); } @Override public void onError(MsalException exception) { /* Failed to acquireToken */ Log.d(TAG, "Authentication failed: " + exception.toString()); displayError(exception); if (exception instanceof MsalClientException) { /* Exception inside MSAL, more info inside MsalError.java */ } else if (exception instanceof MsalServiceException) { /* Exception when communicating with the STS, likely config issue */ } else if (exception instanceof MsalUiRequiredException) { /* Tokens expired or no session, retry with interactive */ } } }; } /** * Callback used for interactive request. * If succeeds we use the access token to call the Microsoft Graph. * Does not check cache. */ 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"); Log.d(TAG, "ID Token: " + authenticationResult.getAccount().getClaims().get("id_token")); /* Update account */ mAccount = authenticationResult.getAccount(); updateUI(); /* call graph */ callGraphAPI(authenticationResult); } @Override public void onError(MsalException exception) { /* Failed to acquireToken */ Log.d(TAG, "Authentication failed: " + exception.toString()); displayError(exception); if (exception instanceof MsalClientException) { /* Exception inside MSAL, more info inside MsalError.java */ } else if (exception instanceof MsalServiceException) { /* Exception when communicating with the STS, likely config issue */ } } @Override public void onCancel() { /* User canceled the authentication */ Log.d(TAG, "User cancelled login."); } }; } /** * Make an HTTP request to obtain MSGraph data */ private void callGraphAPI(final IAuthenticationResult authenticationResult) { MSGraphRequestWrapper.callGraphAPIUsingVolley( getContext(), graphResourceTextView.getText().toString(), authenticationResult.getAccessToken(), new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { /* Successfully called graph, process data and send to UI */ Log.d(TAG, "Response: " + response.toString()); displayGraphResult(response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG, "Error: " + error.toString()); displayError(error); } }); } // // Helper methods manage UI updates // ================================ // displayGraphResult() - Display the graph response // displayError() - Display the graph response // updateSignedInUI() - Updates UI when the user is signed in // updateSignedOutUI() - Updates UI when app sign out succeeds // /** * Display the graph response */ private void displayGraphResult(@NonNull final JSONObject graphResponse) { logTextView.setText(graphResponse.toString()); } /** * Display the error message */ private void displayError(@NonNull final Exception exception) { logTextView.setText(exception.toString()); } /** * Updates UI based on the current account. */ private void updateUI() { if (mAccount != null) { signInButton.setEnabled(false); signOutButton.setEnabled(true); callGraphApiInteractiveButton.setEnabled(true); callGraphApiSilentButton.setEnabled(true); currentUserTextView.setText(mAccount.getUsername()); } else { signInButton.setEnabled(true); signOutButton.setEnabled(false); callGraphApiInteractiveButton.setEnabled(false); callGraphApiSilentButton.setEnabled(false); currentUserTextView.setText("None"); } deviceModeTextView.setText(mSingleAccountApp.isSharedDevice() ? "Shared" : "Non-shared"); } /** * Updates UI when app sign out succeeds */ private void showToastOnSignOut() { final String signOutText = "Signed Out."; currentUserTextView.setText(""); Toast.makeText(getContext(), signOutText, Toast.LENGTH_SHORT) .show(); } }
Ouvrez MainActivity.java et remplacez le code par l’extrait de code suivant pour gérer l’interface utilisateur.
package com.azuresamples.msalandroidapp; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.view.GravityCompat; import android.view.MenuItem; import android.view.View; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import com.google.android.material.navigation.NavigationView; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, OnFragmentInteractionListener{ enum AppFragment { SingleAccount } private AppFragment mCurrentFragment; private ConstraintLayout mContentMain; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContentMain = findViewById(R.id.content_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); //Set default fragment navigationView.setCheckedItem(R.id.nav_single_account); setCurrentFragment(AppFragment.SingleAccount); } @Override public boolean onNavigationItemSelected(final MenuItem item) { final DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { } @Override public void onDrawerOpened(@NonNull View drawerView) { } @Override public void onDrawerClosed(@NonNull View drawerView) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.nav_single_account) { setCurrentFragment(AppFragment.SingleAccount); } drawer.removeDrawerListener(this); } @Override public void onDrawerStateChanged(int newState) { } }); drawer.closeDrawer(GravityCompat.START); return true; } private void setCurrentFragment(final AppFragment newFragment){ if (newFragment == mCurrentFragment) { return; } mCurrentFragment = newFragment; setHeaderString(mCurrentFragment); displayFragment(mCurrentFragment); } private void setHeaderString(final AppFragment fragment){ switch (fragment) { case SingleAccount: getSupportActionBar().setTitle("Single Account Mode"); return; } } private void displayFragment(final AppFragment fragment){ switch (fragment) { case SingleAccount: attachFragment(new com.azuresamples.msalandroidapp.SingleAccountModeFragment()); return; } } private void attachFragment(final Fragment fragment) { getSupportFragmentManager() .beginTransaction() .setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .replace(mContentMain.getId(),fragment) .commit(); } }
Notes
Veillez à mettre à jour le nom du package pour qu’il corresponde au nom de votre package de projet Android.
Layout
Une disposition est un fichier qui définit la structure visuelle et l’apparence d’une interface utilisateur, en spécifiant l’organisation des composants de l’interface utilisateur. Elle est écrite en XML. Les exemples XML suivants sont fournis si vous souhaitez modéliser votre interface utilisateur à partir de ce tutoriel :
Dans app>src>main>res>layout>activity_main.xml. Remplacez le contenu de activity_main.xml par l’extrait de code suivant pour afficher les boutons et les zones de texte :
<?xml version="1.0" encoding="utf-8"?> <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <include layout="@layout/app_bar_main" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer" /> </androidx.drawerlayout.widget.DrawerLayout>
Dans app>src>main>res>layout>app_bar_main.xml. Si vous n’avez pas app_bar_main.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </com.google.android.material.appbar.AppBarLayout> <include layout="@layout/content_main" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Dans app>src>main>res>layout>content_main.xml. Si vous n’avez pas content_main.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content_main" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/app_bar_main"> </androidx.constraintlayout.widget.ConstraintLayout>
Dans app>src>main>res>layout>fragment_m_s_graph_request_wrapper.xml. Si vous n’avez pas fragment_m_s_graph_request_wrapper.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MSGraphRequestWrapper"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
Dans app>src>main>res>layout>fragment_on_interaction_listener.xml. Si vous n’avez pas fragment_on_interaction_listener.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".OnFragmentInteractionListener"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
Dans app>src>main>res>layout>fragment_single_account_mode.xml. Si vous n’avez pas fragment_single_account_mode.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".SingleAccountModeFragment"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".SingleAccountModeFragment"> <LinearLayout android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingBottom="@dimen/activity_vertical_margin"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:layout_gravity="center_vertical" android:textStyle="bold" android:text="Scope" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:layout_weight="7"> <EditText android:id="@+id/scope" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="user.read" android:textSize="12sp" /> <TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:paddingLeft="5dp" android:text="Type in scopes delimited by space" android:textSize="10sp" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:layout_gravity="center_vertical" android:textStyle="bold" android:text="MSGraph Resource URL" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:layout_weight="7"> <EditText android:id="@+id/msgraph_url" android:layout_height="wrap_content" android:layout_width="match_parent" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:textStyle="bold" android:text="Signed-in user" /> <TextView android:id="@+id/current_user" android:layout_width="0dp" android:layout_height="wrap_content" android:paddingLeft="5dp" android:layout_weight="7" android:text="None" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:textStyle="bold" android:text="Device mode" /> <TextView android:id="@+id/device_mode" android:layout_width="0dp" android:layout_height="wrap_content" android:paddingLeft="5dp" android:layout_weight="7" android:text="None" /> </LinearLayout> <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/btn_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/btn_removeAccount" 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/btn_callGraphInteractively" 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/btn_callGraphSilently" 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: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> </LinearLayout> </FrameLayout>
Dans app>src>main>res>layout>nav_header_main.xml. Si vous n’avez pas nav_header_main.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="@dimen/nav_header_height" android:background="@drawable/side_nav_bar" android:gravity="bottom" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:theme="@style/ThemeOverlay.AppCompat.Dark"> <ImageView android:id="@+id/imageView" android:layout_width="66dp" android:layout_height="72dp" android:contentDescription="@string/nav_header_desc" android:paddingTop="@dimen/nav_header_vertical_spacing" app:srcCompat="@drawable/microsoft_logo" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/nav_header_vertical_spacing" android:text="Azure Samples" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="MSAL Android" /> </LinearLayout>
Dans lemenu>src>main>res>l’application>activity_main_drawer.xml. Si vous n’avez pas activity_main_drawer.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:showIn="navigation_view"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_single_account" android:icon="@drawable/ic_single_account_24dp" android:title="Single Account Mode" /> </group> </menu>
Dans app>src>main>res>values>dimens.xml. Remplacez le contenu de dimens.xml avec l’extrait de code suivant :
<resources> <dimen name="fab_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="nav_header_height">176dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen> </resources>
Dans app>src>main>res>values>colors.xml. Remplacez le contenu de colors.xml avec l’extrait de code suivant :
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="purple_200">#FFBB86FC</color> <color name="purple_500">#FF6200EE</color> <color name="purple_700">#FF3700B3</color> <color name="teal_200">#FF03DAC5</color> <color name="teal_700">#FF018786</color> <color name="black">#FF000000</color> <color name="white">#FFFFFFFF</color> <color name="colorPrimary">#008577</color> <color name="colorPrimaryDark">#00574B</color> <color name="colorAccent">#D81B60</color> </resources>
Dans l’application>src>main>res>valeurs>strings.xml. Remplacez le contenu de strings.xml par l’extrait de code suivant :
<resources> <string name="app_name">MSALAndroidapp</string> <string name="action_settings">Settings</string> <!-- Strings used for fragments for navigation --> <string name="first_fragment_label">First Fragment</string> <string name="second_fragment_label">Second Fragment</string> <string name="nav_header_desc">Navigation header</string> <string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string> <string name="next">Next</string> <string name="previous">Previous</string> <string name="hello_first_fragment">Hello first fragment</string> <string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string> <!-- TODO: Remove or change this placeholder text --> <string name="hello_blank_fragment">Hello blank fragment</string> </resources>
Dans app>src>main>res>values>styles.xml. Si vous n’avez pas styles.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources>
Dans app>src>main>res>values>themes.xml. Remplacez le contenu de themes.xml par l’extrait de code suivant :
<resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> <style name="Theme.MSALAndroidapp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> <!-- Secondary brand color. --> <item name="colorSecondary">@color/teal_200</item> <item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorOnSecondary">@color/black</item> <!-- Status bar color. --> <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item> <!-- Customize your theme here. --> </style> <style name="Theme.MSALAndroidapp.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="Theme.MSALAndroidapp.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="Theme.MSALAndroidapp.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources>
Dans app>src>main>res>drawable>ic_single_account_24dp.xml. Si vous n’avez pas ic_single_account_24dp.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/> </vector>
Dans app>src>main>res>drawable>side_nav_bar.xml. Si vous n’avez pas side_nav_bar.xml dans votre dossier, créez et ajoutez l’extrait de code suivant :
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:angle="135" android:centerColor="#009688" android:endColor="#00695C" android:startColor="#4DB6AC" android:type="linear" /> </shape>
Dans app>src>main>res>drawable. Dans le dossier, ajoutez un logo Microsoft png nommé
microsoft_logo.png
.
La déclaration de votre interface utilisateur au format XML vous permet de séparer la présentation de votre application du code qui contrôle son comportement. Pour en savoir plus sur la disposition Android, consultez Dispositions
Test de l'application
Exécution locale
Générez et déployez l’application sur un appareil de test ou un émulateur. Vous devez pouvoir vous connecter et obtenir des jetons pour les comptes Microsoft Entra ID ou les comptes personnels Microsoft.
Une fois que vous êtes connecté, l’application affiche les données retournées par le point de terminaison /me
Microsoft Graph.
Consentement
La première fois qu’un utilisateur se connecte à votre application, Microsoft Identity l’invitera à accepter les autorisations demandées. Certains locataires Microsoft Entra ont désactivé le consentement de l’utilisateur, ce qui oblige les administrateurs à donner leur consentement au nom de tous les utilisateurs. Pour prendre en charge ce scénario, vous devez créer votre propre locataire ou recevoir le consentement administrateur.
Nettoyer les ressources
Quand vous n’en avez plus besoin, supprimez l’objet d’application que vous avez créé à l’étape Inscrivez votre application.
Aide et support
Si vous avez besoin d’aide, si vous souhaitez signaler un problème ou si vous voulez en savoir plus sur vos options de support, consultez Aide et support pour les développeurs.
Étapes suivantes
Pour explorer des scénarios plus complexes, consultez cet exemple de code fonctionnel complet sur GitHub.
Pour obtenir plus d’informations sur la création d’applications mobiles qui appellent des API web protégées dans notre série de scénarios en plusieurs parties, consulter :