Sign in users and call the Microsoft Graph from an Android app

In this tutorial, you'll learn how to build an Android application and integrate it into Microsoft identity platform. Specifically, this app will sign in a user, get an access token to call the Microsoft Graph API, and make a basic request to the Microsoft Graph API.

When you've completed the guide, your application will accept sign-ins of personal Microsoft accounts (including outlook.com, live.com, and others) and work or school accounts from any company or organization that uses Azure Active Directory.

How the sample app generated by this guide works

How this sample works

The app in this sample will sign in users and get data on their behalf. This data will be accessed via a remote API (Microsoft Graph API in this case) that requires authorization and is also protected by Microsoft identity platform.

More specifically:

  • Your app will launch a web page to sign in the user.
  • 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 be coordinating and helping with Auth. MSAL will automatically renew tokens, deliver SSO between other apps on the device, help manage the Account(s), and handle most Conditional Access cases.

Prerequisites

  • This guided setup uses Android Studio 3.0.
  • Android 21 or later is required (25+ is recommended).
  • Google Chrome or a web browser that uses Custom Tabs is required for this version of MSAL for Android.

Library

This guide uses the following authentication library:

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), which handles all token operations including acquiring, caching, refreshing, and deleting. The tokens are needed to access the APIs protected by the Microsoft identity platform.

Create the app's 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

  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 launches a window that prompts users to sign in or select their account. Applications generally will need to ask the user for an initial interaction, but can operate silently from that point on.

Get a user token silently

The AcquireTokenSilentAsync method gets a token without any user interaction. AcquireTokenSilentAsync can be treated as a best-effort request, with a fallback to AcquireTokenAsync when the user needs to sign in again or do some extra authorization, like multi-factor auth.

When AcquireTokenSilentAsync fails, it will generate a MsalUiRequiredException. Your application can handle this exception in two ways:

  • Call AcquireTokenAsync immediately. This call results in prompting the user to sign in. This pattern is used in online applications where there is no available offline content for the user. The sample generated by this tutorial follows this pattern, which you can see in action the first time you execute the sample.
  • Present a visual indication to users that an interactive sign-in is required. Call AcquireTokenAsync when the user is ready.
  • Retry AcquireTokenSilentAsync later. This pattern is often used when users can use other application functionality without disruption--for example, when offline content is available in the application. The application can decide to retry AcquireTokenSilentAsync when the network is restored after having been temporarily unavailable.

Call the Microsoft Graph API

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() uses getAccessToken() to get the fresh access token. The app uses the token in an HTTP GET request against the Microsoft Graph API.

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 removes users from the MSAL cache. MSAL will no longer have any state for the signed in user, and they'll be logged out of the application.

More information on multi-account scenarios

MSAL also supports scenarios when multiple accounts are signed in at the same time. For example, many email apps allow multiple accounts to be signed in at the same time.

Register your application

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

Option 1: Express

  1. Go to the Microsoft Application Registration Portal.
  2. In Application Name, 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

  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 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] with your Application / Client ID:

    final static String CLIENT_ID = "[Enter the application Id here]";
    
  7. Under app > manifests, open the AndroidManifest.xml file.
  8. In the manifest\application, add the following activity. The BrowserTabActivity activity that allows Microsoft to call back to 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, replace [Enter the application Id here] with the Application / Client ID.

Test your app

  1. Run your code to your device/emulator.
  2. Try to sign in with a Azure Active Directory account (work or school account) or Microsoft account (live.com, outlook.com).

    Test your application

    Enter username and password

The first time a user signs in to your application, they will be prompted to consent to the permissions your app needs, as shown here:

Provide your consent for application access

Success!

After you sign in & consent, the app will display response from the Microsoft Graph API. This specific call is to the /me endpoint and returns the user profile. For a list of other Microsoft Graph endpoints, see Microsoft Graph API developer documentation.

Scopes and delegated permissions

The Microsoft Graph API requires the User.Read scope to read a user's profile. This scope is automatically in every app that's registered in the Application Registration Portal. Other APIs will require additional scopes. For example, the Microsoft Graph API requires the Calendars.Read scope to list the user’s calendars.

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

Note

Your users may be prompted for additional consent if you change your app registration.

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: