-
Under app > java > {domain}.{appname}, open
MainActivity
. -
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.*;
-
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); } }
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).
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 anMsalUiRequiredException
exception is thrown. - The code in the sample then handles the exception by calling
AcquireTokenAsync
, which results in prompting the user to sign in.
- Because no user has used the application,
-
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 retryAcquireTokenSilentAsync
when the network is restored after having been temporarily unavailable.
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());
}
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.
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");
}
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.