Call the Microsoft Graph API from an Android app

This guide demonstrates how a native Android application can get an access token and call the Microsoft Graph API or other APIs that require access tokens from the Azure Active Directory v2 endpoint.

When you've completed the guide, your application will be able to accept sign-ins of personal accounts (including outlook.com, live.com, and others) and work and school accounts from any company or organization that uses Azure Active Directory. The application will then call an API that's protected by the Azure Active Directory v2 endpoint.

How this sample works

How this sample works

The sample application that you create with this guide is based on a scenario where an Android application is used to query a Web API that accepts tokens from the Azure Active Directory v2 endpoint (Microsoft Graph API, in this case). For this scenario, your application adds the acquired token to HTTP requests via the Authorization header. The Microsoft Authentication Library (MSAL) handles the token acquisition and renewal for you.

Prerequisites

  • This Guided Setup is focused on Android Studio, but any other Android application development environment is also acceptable.
  • Android SDK 21 or later is required (SDK 25 is recommended).
  • Google Chrome or a web browser that uses Custom Tabs is required for this release of MSAL for Android.

Note

Google Chrome is not included with Visual Studio Emulator for Android. We recommend that you test this code on an Emulator with API 25 or an image with API 21 or newer that has Google Chrome installed.

Handling token acquisition for accessing protected Web APIs

After the user is authenticated, the sample application receives an access token that can be used to query Microsoft Graph API or a Web API that's secured by Azure Active Directory v2.

APIs such as Microsoft Graph require an access token to allow access to specific resources. For example, an access token is required to read a user’s profile, access a user’s calendar, or send email. Your application can request an access token by using MSAL to access these resources by specifying API scopes. This access token is then added to the HTTP Authorization header for every call that's made against the protected resource.

MSAL manages caching and refreshing access tokens for you, so that your application doesn't need to.

Libraries

This guide uses the following libraries:

Library Description
com.microsoft.identity.client Microsoft Authentication Library (MSAL)

Set up your project

Do you want to download this sample's Android Studio project instead? Download a project, and skip to the Configuration step to configure the code sample before you execute it.

Create a new project

  1. Open Android Studio, and then select File > New > New Project.
  2. Name your application, and then select Next.
  3. Select API 21 or newer (Android 5.0), and then select Next.
  4. Leave Empty Activity as it is, select Next, and then select Finish.

Add MSAL to your project

  1. In Android Studio, select Gradle Scripts > build.gradle (Module: app).
  2. Under Dependencies, paste the following code:

    compile ('com.microsoft.identity.client:msal:0.1.+') {
        exclude group: 'com.android.support', module: 'appcompat-v7'
    }
    compile 'com.android.volley:volley:1.0.0'
    

About this package

The package in the preceding code installs Microsoft Authentication Library. MSAL handles acquiring, caching and refreshing user tokens that are used to access the APIs that are protected by the Azure Active Directory v2 endpoint.

Create the application UI

  1. Go to res > layout, and then open activity_main.xml.
  2. Change the activity layout from android.support.constraint.ConstraintLayout or other to LinearLayout.
  3. Add the android:orientation="vertical" property to the LinearLayout node.
  4. Paste the following code into the LinearLayout node, replacing the current content:

    <TextView
        android:text="Welcome, "
        android:textColor="#3f3f3f"
        android:textSize="50px"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="15dp"
        android:id="@+id/welcome"
        android:visibility="invisible"/>
    
    <Button
        android:id="@+id/callGraph"
        android:text="Call Microsoft Graph"
        android:textColor="#FFFFFF"
        android:background="#00a1f1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="200dp"
        android:textAllCaps="false" />
    
    <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"/>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:gravity="center|bottom"
        android:orientation="vertical" >
    
        <Button
            android:text="Sign Out"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="15dp"
            android:textColor="#FFFFFF"
            android:background="#00a1f1"
            android:textAllCaps="false"
            android:id="@+id/clearCache"
            android:visibility="invisible" />
    </LinearLayout>
    

Use MSAL to get a token for the Microsoft Graph API

  1. Under app > java > {domain}.{appname}, open MainActivity.
  2. Add the following imports:

    import android.app.Activity;
    import android.content.Intent;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    import com.android.volley.*;
    import com.android.volley.toolbox.JsonObjectRequest;
    import com.android.volley.toolbox.Volley;
    import org.json.JSONObject;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import com.microsoft.identity.client.*;
    
  3. Replace the MainActivity class with following code:

    public class MainActivity extends AppCompatActivity {
    
        final static String CLIENT_ID = "[Enter the application Id here]";
        final static String SCOPES [] = {"https://graph.microsoft.com/User.Read"};
        final static String MSGRAPH_URL = "https://graph.microsoft.com/v1.0/me";
    
        /* UI & Debugging Variables */
        private static final String TAG = MainActivity.class.getSimpleName();
        Button callGraphButton;
        Button signOutButton;
    
        /* Azure AD Variables */
        private PublicClientApplication sampleApp;
        private AuthenticationResult authResult;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            callGraphButton = (Button) findViewById(R.id.callGraph);
            signOutButton = (Button) findViewById(R.id.clearCache);
    
            callGraphButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    onCallGraphClicked();
                }
            });
    
            signOutButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    onSignOutClicked();
                }
            });
    
    /* Configure your sample app and save state for this activity */
            sampleApp = null;
            if (sampleApp == null) {
                sampleApp = new PublicClientApplication(
                        this.getApplicationContext(),
                        CLIENT_ID);
            }
    
    /* Attempt to get a user and acquireTokenSilent
    * If this fails we do an interactive request
    */
            List<User> users = null;
    
            try {
                users = sampleApp.getUsers();
    
                if (users != null && users.size() == 1) {
            /* We have 1 user */
    
                    sampleApp.acquireTokenSilentAsync(SCOPES, users.get(0), getAuthSilentCallback());
                } else {
            /* We have no user */
    
            /* Let's do an interactive request */
                    sampleApp.acquireToken(this, SCOPES, getAuthInteractiveCallback());
                }
            } catch (MsalClientException e) {
                Log.d(TAG, "MSAL Exception Generated while getting users: " + e.toString());
    
            } catch (IndexOutOfBoundsException e) {
                Log.d(TAG, "User at this position does not exist: " + e.toString());
            }
    
        }
    
    //
    // App callbacks for MSAL
    // ======================
    // getActivity() - returns activity so we can acquireToken within a callback
    // getAuthSilentCallback() - callback defined to handle acquireTokenSilent() case
    // getAuthInteractiveCallback() - callback defined to handle acquireToken() case
    //
    
        public Activity getActivity() {
            return this;
        }
    
        /* Callback method for acquireTokenSilent calls 
        * Looks if tokens are in the cache (refreshes if necessary and if we don't forceRefresh)
        * else errors that we need to do an interactive request.
        */
        private AuthenticationCallback getAuthSilentCallback() {
            return new AuthenticationCallback() {
                @Override
                public void onSuccess(AuthenticationResult authenticationResult) {
                /* Successfully got a token, call Graph now */
                    Log.d(TAG, "Successfully authenticated");
    
                /* Store the authResult */
                    authResult = authenticationResult;
    
                /* call graph */
                    callGraphAPI();
    
                /* update the UI to post call Graph state */
                    updateSuccessUI();
                }
    
                @Override
                public void onError(MsalException exception) {
                /* Failed to acquireToken */
                    Log.d(TAG, "Authentication failed: " + exception.toString());
    
                    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 */
                    }
                }
    
                @Override
                public void onCancel() {
                /* User cancelled the authentication */
                    Log.d(TAG, "User cancelled login.");
                }
            };
        }
    
        /* 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(AuthenticationResult authenticationResult) {
                /* Successfully got a token, call graph now */
                    Log.d(TAG, "Successfully authenticated");
                    Log.d(TAG, "ID Token: " + authenticationResult.getIdToken());
    
                /* Store the auth result */
                    authResult = authenticationResult;
    
                /* call Graph */
                    callGraphAPI();
    
                /* update the UI to post call Graph state */
                    updateSuccessUI();
                }
    
                @Override
                public void onError(MsalException exception) {
                /* Failed to acquireToken */
                    Log.d(TAG, "Authentication failed: " + exception.toString());
    
                    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 cancelled the authentication */
                    Log.d(TAG, "User cancelled login.");
                }
            };
        }
    
        /* Set the UI for successful token acquisition data */
        private void updateSuccessUI() {
            callGraphButton.setVisibility(View.INVISIBLE);
            signOutButton.setVisibility(View.VISIBLE);
            findViewById(R.id.welcome).setVisibility(View.VISIBLE);
            ((TextView) findViewById(R.id.welcome)).setText("Welcome, " +
                    authResult.getUser().getName());
            findViewById(R.id.graphData).setVisibility(View.VISIBLE);
        }
    
        /* Use MSAL to acquireToken for the end-user
        * Callback will call Graph api w/ access token & update UI
        */
        private void onCallGraphClicked() {
            sampleApp.acquireToken(getActivity(), SCOPES, getAuthInteractiveCallback());
        }
    
        /* Handles the redirect from the System Browser */
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            sampleApp.handleInteractiveRequestRedirect(requestCode, resultCode, data);
        }
    
    }
    

More information

Get a user token interactively

Calling the AcquireTokenAsync method results in a window that prompts users to sign in. Applications usually require users to sign in interactively the first time they need to access a protected resource. They might also need to sign in when a silent operation to acquire a token fails (for example, when a user’s password is expired).

Get a user token silently

The AcquireTokenSilentAsync method handles token acquisitions and renewals without any user interaction. After AcquireTokenAsync is executed for the first time, AcquireTokenSilentAsync is the usual method to use to obtain tokens that access protected resources for subsequent calls, because calls to request or renew tokens are made silently.

Eventually, the AcquireTokenSilentAsync method will fail. Reasons for failure might be that the user has either signed out or changed their password on another device. When MSAL detects that the issue can be resolved by requiring an interactive action, it fires an MsalUiRequiredException exception. Your application can handle this exception in two ways:

  • It can make a call against AcquireTokenAsync immediately. This call results in prompting the user to sign in. This pattern is usually used in online applications where there is no available offline content for the user. The sample generated by this guided setup follows this pattern, which you can see in action the first time you execute the sample.

    • Because no user has used the application, PublicClientApp.Users.FirstOrDefault() contains a null value, and an MsalUiRequiredException exception is thrown.
    • The code in the sample then handles the exception by calling AcquireTokenAsync, which results in prompting the user to sign in.
  • It can instead present a visual indication to users that an interactive sign-in is required, so that they can select the right time to sign in. Or the application can retry AcquireTokenSilentAsync later. This pattern is frequently used when users can use other application functionality without disruption--for example, when offline content is available in the application. In this case, users can decide when they want to sign in to either access the protected resource or refresh the outdated information. Alternatively, the application can decide to retry AcquireTokenSilentAsync when the network is restored after having been temporarily unavailable.

Call the Microsoft Graph API by using the token you just obtained

Add the following methods into the MainActivity class:

/* Use Volley to make an HTTP request to the /me endpoint from MS Graph using an access token */
private void callGraphAPI() {
    Log.d(TAG, "Starting volley request to graph");

    /* Make sure we have a token to send to graph */
    if (authResult.getAccessToken() == null) {return;}

    RequestQueue queue = Volley.newRequestQueue(this);
    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, MSGRAPH_URL,
            parameters,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());

            updateGraphUI(response);
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.d(TAG, "Error: " + error.toString());
        }
    }) {
        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "Bearer " + authResult.getAccessToken());
            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);
}

/* Sets the Graph response */
private void updateGraphUI(JSONObject graphResponse) {
    TextView graphText = (TextView) findViewById(R.id.graphData);
    graphText.setText(graphResponse.toString());
}

More information about making a REST call against a protected API

In this sample application, callGraphAPI calls getAccessToken and then makes an HTTP GET request against a resource that requires a token and returns the content. This method adds the acquired token in the HTTP Authorization header. For this sample, the resource is the Microsoft Graph API me endpoint, which displays the user's profile information.

Set up sign-out

Add the following methods into the MainActivity class:

/* Clears a user's tokens from the cache.
 * Logically similar to "sign out" but only signs out of this app.
 */
private void onSignOutClicked() {

    /* Attempt to get a user and remove their cookies from cache */
    List<User> users = null;

    try {
        users = sampleApp.getUsers();

        if (users == null) {
            /* We have no users */

        } else if (users.size() == 1) {
            /* We have 1 user */
            /* Remove from token cache */
            sampleApp.remove(users.get(0));
            updateSignedOutUI();

        }
        else {
            /* We have multiple users */
            for (int i = 0; i < users.size(); i++) {
                sampleApp.remove(users.get(i));
            }
        }

        Toast.makeText(getBaseContext(), "Signed Out!", Toast.LENGTH_SHORT)
                .show();

    } catch (MsalClientException e) {
        Log.d(TAG, "MSAL Exception Generated while getting users: " + e.toString());

    } catch (IndexOutOfBoundsException e) {
        Log.d(TAG, "User at this position does not exist: " + e.toString());
    }
}

/* Set the UI for signed-out user */
private void updateSignedOutUI() {
    callGraphButton.setVisibility(View.VISIBLE);
    signOutButton.setVisibility(View.INVISIBLE);
    findViewById(R.id.welcome).setVisibility(View.INVISIBLE);
    findViewById(R.id.graphData).setVisibility(View.INVISIBLE);
    ((TextView) findViewById(R.id.graphData)).setText("No Data");
}

More information about user sign-out

The onSignOutClicked method in the preceding code removes users from the MSAL user cache, which effectively tells MSAL to forget the current user so that a future request to acquire a token will succeed only if it is made to be interactive.

Although the application in this sample supports single users, MSAL supports scenarios where multiple accounts can be signed in at the same time. An example is an email application where a user has multiple accounts.

Register your application

You can register your application in either of two ways, as described in the next two sections.

Option 1: Express mode

You can quickly register your application by doing the following:

  1. Go to the Microsoft Application Registration Portal.
  2. In the Application Name box, enter a name for your application.

  3. Ensure that the Guided Setup check box is selected, and then select Create.

  4. Follow the instructions for obtaining the application ID, and paste it into your code.

Option 2: Advanced mode

To register your application and add your application registration information to your solution, do the following:

  1. If you haven't already registered your application, go to the Microsoft Application Registration Portal.
  2. In the Application Name box, enter a name for your application.

  3. Ensure that the Guided Setup check box is cleared, and then select Create.

  4. Select Add Platform, select Native Application, and then select Save.

  5. Under app > java > {host}.{namespace}, open MainActivity.

  6. Replace [Enter the application Id here] in the following line with the application ID that you just registered:

    final static String CLIENT_ID = "[Enter the application Id here]";
    
  7. Under app > manifests, open the AndroidManifest.xml file.

  8. In the manifest\application node, add the following activity. Doing so registers a BrowserTabActivity activity that allows the OS to resume your application after it completes the authentication:

    <!--Intent filter to capture System Browser 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" />
    
            <!--Add in your scheme/host from registered redirect URI-->
            <!--By default, the scheme should be similar to 'msal[appId]' -->
            <data android:scheme="msal[Enter the application Id here]"
                android:host="auth" />
        </intent-filter>
    </activity>
    
  9. In the BrowserTabActivity node, replace [Enter the application Id here] with the application ID.

Test your code

  1. Deploy your code to your device/emulator.

  2. When you're ready to test your application, use an Azure Active Directory account (work or school account) or a Microsoft account (live.com, outlook.com) to sign in.

    Test your application

    Enter username and password

The first time that you sign in to your application, you're also prompted to provide consent to allow the application to access your profile and sign you in, as shown here:

Provide your consent for application access

View application results

After you sign in, you should see the results that are returned by the call to the Microsoft Graph API. The call to the Microsoft Graph API me endpoint returns the user profile. For a list of common Microsoft Graph endpoints, see Microsoft Graph API developer documentation.

More information about scopes and delegated permissions

The Microsoft Graph API requires the user.read scope to read a user's profile. This scope is automatically added by default in every application that's registered in the Application Registration Portal. Other APIs for Microsoft Graph, as well as custom APIs for your back-end server, might require additional scopes. The Microsoft Graph API requires the Calendars.Read scope to list the user’s calendars.

To access the user’s calendars in the context of an application, add the Calendars.Read delegated permission to the application registration information. Then, add the Calendars.Read scope to the acquireTokenSilent call.

Note

The user might be prompted for additional consents as you increase the number of scopes.

Help and support

If you need help, want to report an issue, or want to learn more about your support options, see the following article: