Tutorial: Sign in users and call the Microsoft Graph API from an Android application
In this tutorial, you build an Android app that integrates with the Microsoft identity platform to sign in users and get an access token to call the Microsoft Graph API.
When you've completed this tutorial, your application will accept sign-ins of personal Microsoft accounts (including outlook.com, live.com, and others) as well as work or school accounts from any company or organization that uses Azure Active Directory.
In this tutorial:
- Create an Android app project in Android Studio
- Register the app in the Azure portal
- Add code to support user sign-in and sign-out
- Add code to call the Microsoft Graph API
- Test the app
Prerequisites
- Android Studio 3.5+
How this tutorial works
The app in this tutorial will sign in users and get data on their behalf. This data will be accessed through a protected API (Microsoft Graph API) that requires authorization and is protected by the Microsoft identity platform.
More specifically:
- Your app will sign in the user either through a browser or the Microsoft Authenticator and Intune Company Portal.
- The end user will accept the permissions your application has requested.
- Your app will be issued an access token for the Microsoft Graph API.
- The access token will be included in the HTTP request to the web API.
- Process the Microsoft Graph response.
This sample uses the Microsoft Authentication Library for Android (MSAL) to implement Authentication: com.microsoft.identity.client.
MSAL will automatically renew tokens, deliver single sign-on (SSO) between other apps on the device, and manage the Account(s).
This tutorial demonstrates simplified examples of working with MSAL for Android. For simplicity, it uses Single Account Mode only. To explore more complex scenarios, see a completed working code sample on GitHub.
Create a project
If you do not already have an Android application, follow these steps to set up a new project.
- Open Android Studio, and select Start a new Android Studio project.
- Select Basic Activity and select Next.
- Name your application.
- Save the package name. You will enter it later into the Azure portal.
- Change the language from Kotlin to Java.
- Set the Minimum API level to API 19 or higher, and click Finish.
- In the project view, choose Project in the dropdown to display source and non-source project files, open app/build.gradle and set
targetSdkVersion
to28
.
Integrate with the Microsoft Authentication Library
Register your application
Sign in to the Azure portal.
If you have access to multiple tenants, use the Directories + subscriptions filter
in the top menu to switch to the tenant in which you want to register the application.
Search for and select Azure Active Directory.
Under Manage, select App registrations > New registration.
Enter a Name for your application. Users of your app might see this name, and you can change it later.
Select Register.
Under Manage, select Authentication > Add a platform > Android.
Enter your project's Package Name. If you downloaded the code, this value is
com.azuresamples.msalandroidapp
.In the Signature hash section of the Configure your Android app page, select Generating a development Signature Hash. and copy the KeyTool command to use for your platform.
KeyTool.exe is installed as part of the Java Development Kit (JDK). You must also install the OpenSSL tool to execute the KeyTool command. Refer to the Android documentation on generating a key for more information.
Enter the Signature hash generated by KeyTool.
Select Configure and save the MSAL Configuration that appears in the Android configuration page so you can enter it when you configure your app later.
Select Done.
Configure your application
In Android Studio's project pane, navigate to app\src\main\res.
Right-click res and choose New > Directory. Enter
raw
as the new directory name and click OK.In app > src > main > res > raw, create a new JSON file called
auth_config_single_account.json
and paste the MSAL Configuration that you saved earlier.Below the redirect URI, paste:
"account_mode" : "SINGLE",
Your config file should resemble this example:
{ "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" } } ] }
This tutorial only demonstrates how to configure an app in Single Account mode. View the documentation for more information on single vs. multiple account mode and configuring your app
In app > src > main > AndroidManifest.xml, add the
BrowserTabActivity
activity below to the application body. This entry allows Microsoft to call back to your application after it completes the authentication:<!--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>
Substitute the package name you registered in the Azure portal for the
android:host=
value. Substitute the key hash you registered in the Azure portal for theandroid:path=
value. The Signature Hash should not be URL-encoded. Ensure that there is a leading/
at the beginning of your Signature Hash.The "Package Name" you will replace the
android:host
value with should look similar to:com.azuresamples.msalandroidapp
. The "Signature Hash" you will replace yourandroid:path
value with should look similar to:/1wIqXSqBj7w+h11ZifsnqwgyKrY=
.You will also be able to find these values in the Authentication blade of your app registration. Note that your redirect URI will look similar to:
msauth://com.azuresamples.msalandroidapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3D
. While the Signature Hash is URL-encoded at the end of this value, the Signature Hash should not be URL-encoded in yourandroid:path
value.
Use MSAL
Add MSAL to your project
In the Android Studio project window, navigate to app > build.gradle and add the following:
apply plugin: 'com.android.application' allprojects { repositories { mavenCentral() google() mavenLocal() maven { url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1' } maven { name "vsts-maven-adal-android" url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username System.getenv("ENV_VSTS_MVN_ANDROIDADAL_USERNAME") != null ? System.getenv("ENV_VSTS_MVN_ANDROIDADAL_USERNAME") : project.findProperty("vstsUsername") password System.getenv("ENV_VSTS_MVN_ANDROIDADAL_ACCESSTOKEN") != null ? System.getenv("ENV_VSTS_MVN_ANDROIDADAL_ACCESSTOKEN") : project.findProperty("vstsMavenAccessToken") } } jcenter() } } dependencies{ implementation 'com.microsoft.identity.client:msal:2.+' implementation 'com.microsoft.graph:microsoft-graph:1.5.+' } packagingOptions{ exclude("META-INF/jersey-module-version") }
Required Imports
Add the following to the top of 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.*;
Instantiate PublicClientApplication
Initialize 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
Inside the MainActivity
class, refer to the following onCreate() method to instantiate MSAL using the 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
Listen to buttons and call methods or log errors accordingly.
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
Signing out with MSAL removes all known information about a user from the application, but the user will still have an active session on their device. If the user attempts to sign in again they may see sign-in UI, but may not need to reenter their credentials because the device session is still active.
getAuthInteractiveCallback
Callback used for interactive requests.
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
Callback used for silent requests
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);
}
};
}
Call Microsoft Graph API
The following code demonstrates how to call the GraphAPI using the Graph SDK.
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);
}
});
}
Add UI
Activity
If you would like to model your UI off this tutorial, the following methods provide a guide to updating text and listening to buttons.
updateUI
Enable/disable buttons based on sign-in state and set text.
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
Method to update text in UI to reflect sign out.
private void performOperationOnSignOut() {
final String signOutText = "Signed Out.";
currentUserTextView.setText("");
Toast.makeText(getApplicationContext(), signOutText, Toast.LENGTH_SHORT)
.show();
}
Layout
Sample activity_main.xml
file to display buttons and text boxes.
<?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 your app
Run locally
Build and deploy the app to a test device or emulator. You should be able to sign in and get tokens for Azure AD or personal Microsoft accounts.
After you sign in, the app will display the data returned from the Microsoft Graph /me
endpoint.
PR 4
Consent
The first time any user signs into your app, they will be prompted by Microsoft identity to consent to the permissions requested. Some Azure AD tenants have disabled user consent which requires admins to consent on behalf of all users. To support this scenario, you will either need to create your own tenant or receive admin consent.
Clean up resources
When no longer needed, delete the app object that you created in the Register your application step.
Help and support
If you need help, want to report an issue, or want to learn about your support options, see Help and support for developers.
Next steps
Learn more about building mobile apps that call protected web APIs in our multi-part scenario series.
Feedback
Submit and view feedback for