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 d’identité Microsoft pour connecter des utilisateurs et obtenir un jeton d’accès pour appeler l’API Microsoft Graph.

À la fin de ce tutoriel, votre application acceptera les connexions de comptes Microsoft personnels (notamment outlook.com, live.com et d’autres) ainsi que de comptes professionnels ou scolaires de toute entreprise ou organisation utilisant Azure Active Directory.

Dans ce tutoriel :

  • Créer un projet d’application Android dans Android Studio
  • Inscrire l'application sur le portail Azure
  • 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

  • Android Studio 3.5+

Fonctionnement de ce tutoriel

Shows how the sample app generated by this tutorial works

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.

Plus précisément :

  • Votre application connectera l’utilisateur via un navigateur ou Microsoft Authenticator et le portail d’entreprise Intune.
  • L’utilisateur final acceptera les autorisations que votre application a demandées.
  • Votre application émet un jeton d’accès pour l’API Microsoft Graph.
  • Le jeton d’accès est inclus dans la requête HTTP adressée à l’API web.
  • La réponse Microsoft Graph est traitée.

Cet exemple utilise la bibliothèque d’authentification Microsoft pour Android (MSAL) pour implémenter l’authentification : com.microsoft.identity.client.

MSAL renouvelle automatiquement les jetons, fournit une authentification unique (SSO) entre les autres applications de l’appareil et gère les comptes.

Ce tutoriel présente des exemples simplifiés montrant comment utiliser MSAL pour Android. Par souci de simplicité, il utilise uniquement le mode monocompte. Pour explorer des scénarios plus complexes, consultez cet exemple de code fonctionnel complet sur GitHub.

Création d’un projet

Si vous n’avez pas encore d’application Android, effectuez les étapes suivantes pour configurer un nouveau projet.

  1. Ouvrez Android Studio, puis sélectionnez Démarrer un nouveau projet Android Studio.
  2. Sélectionnez Activité de base, puis Suivant.
  3. Donnez un nom à votre application.
  4. Enregistrez le nom du package. Vous allez l’entrer plus tard dans le portail Azure.
  5. Remplacez le langage Kotlin par Java.
  6. Affectez à Niveau d’API minimal la valeur API 19 ou une valeur supérieure, puis cliquez sur Terminer.
  7. Dans la vue du projet, dans la liste déroulante, choisissez Projet pour afficher les fichiers projet sources et non-sources, ouvrez app/build.gradle, puis affectez à targetSdkVersion la valeur 28.

Intégrer à l’aide de la bibliothèque d’authentification Microsoft

Inscrivez votre application

  1. Connectez-vous au portail Azure.

  2. Si vous avez accès à plusieurs locataires, utilisez le filtre Répertoires + abonnements dans le menu du haut pour basculer vers le locataire dans lequel vous voulez inscrire l’application.

  3. Recherchez et sélectionnez Azure Active Directory.

  4. Sous Gérer, sélectionnez Inscriptions d’applications>Nouvelle inscription.

  5. Entrez un nom pour votre application. Les utilisateurs de votre application peuvent voir ce nom, et vous pouvez le changer ultérieurement.

  6. Sélectionnez Inscription.

  7. Sous Gérer, sélectionnez Authentification>Ajouter une plateforme>Android.

  8. Entrez le nom du package de votre projet. Si vous avez téléchargé le code, cette valeur est com.azuresamples.msalandroidapp.

  9. 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 à utiliser pour votre plateforme.

    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, reportez-vous à la documentation Android sur la génération d’une clé.

  10. Entrez le Hachage de signature généré par KeyTool.

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

  12. Sélectionnez Terminé.

Configuration de votre application

  1. Dans le volet de projet d’Android Studio, accédez à app\src\main\res.

  2. Cliquez avec le bouton droit sur res, puis choisissez Nouveau>Répertoire. Entrez raw en tant que nouveau nom de répertoire, puis cliquez sur OK.

  3. 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" : "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"
          }
        }
      ]
    }
    

    Ce tutoriel montre uniquement comment configurer une application en mode monocompte. Consultez la documentation pour plus d’informations sur le mode monocompte ou multicompte et la configuration de votre application.

  4. Dans app>src>main>AndroidManifest.xml, ajoutez l’activité BrowserTabActivity ci-dessous au corps de l’application. Cette entrée permet à Microsoft 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">
        <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>
    

    Remplacez le nom de package que vous avez inscrit dans le portail Azure pour la valeur android:host=. Remplacez le hachage de clé que vous avez inscrit dans le portail Azure pour la valeur android:path=. Le code de hachage de la signature ne doit pas être encodé par URL. Assurez-vous de la présence d’un caractère / de début dans votre hachage de signature.

    Le « nom du package » par lequel vous remplacez la valeur android:host doit ressembler à ceci : com.azuresamples.msalandroidapp. Le « code de hachage de la signature » par lequel vous remplacez votre valeur android:path doit ressembler à ce qui suit : /1wIqXSqBj7w+h11ZifsnqwgyKrY=.

    Vous pouvez également trouver ces valeurs dans le panneau Authentification de l’inscription de votre application. Notez que votre URI de redirection doit ressembler à ce qui suit : msauth://com.azuresamples.msalandroidapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3D. Le code de hachage de la signature est encodé avec une URL à la fin de cette valeur, mais il ne doit pas être encodé avec une URL dans votre valeur android:path.

Utiliser MSAL

Ajouter MSAL à votre projet

  1. Dans la fenêtre de projet Android Studio, accédez à app>build.gradle, puis ajoutez ce qui suit :

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

    Informations complémentaires sur le kit SDK Microsoft Graph

Importations nécessaires

Ajoutez le code suivant au début de 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.*;

Instancier PublicClientApplication

Initialiser les variables

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

Dans la classe MainActivity, reportez-vous à la méthode onCreate() suivante pour instancier MSAL à l’aide de SingleAccountPublicClientApplication.

@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

Soyez à l’écoute des boutons et appelez les méthodes ou consignez les erreurs en conséquence.

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

Important

La déconnexion de MSAL entraîne la suppression de toutes les informations connues sur un utilisateur dans l’application. Toutefois, l’utilisateur a toujours une session active sur son appareil. Si l’utilisateur tente de se reconnecter, il peut éventuellement voir l’IU de connexion, mais il n’est pas obligé de ressaisir ses informations d’identification, car la session sur l’appareil est toujours active.

getAuthInteractiveCallback

Rappel utilisé pour les demandes interactives

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

Rappel utilisé pour les demandes silencieuses

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

Appeler l’API Microsoft Graph

Le code suivant montre comment appeler l’API Graph à l’aide du kit SDK Graph.

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

Ajouter une IU

Activité

Si vous souhaitez modéliser votre interface utilisateur en dehors de ce tutoriel, les méthodes suivantes fournissent un guide pour la mise à jour du texte et l’écoute des boutons.

updateUI

Activez/désactivez les boutons en fonction de l’état de connexion et du texte défini.

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

Méthode permettant de mettre à jour le texte dans l’interface utilisateur pour refléter la déconnexion.

private void performOperationOnSignOut() {
    final String signOutText = "Signed Out.";
    currentUserTextView.setText("");
    Toast.makeText(getApplicationContext(), signOutText, Toast.LENGTH_SHORT)
            .show();
}

Mise en page

Exemple de fichier activity_main.xml permettant d’afficher des boutons et des zones de texte.

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

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 Azure AD 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. PR 4

La première fois qu’un utilisateur se connecte à votre application, Microsoft Identity l’invitera à accepter les autorisations demandées. Certains locataires Azure AD 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 de l’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

Apprenez-en davantage 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.