Reputation: 11
I'm using MSAL to sign users into an Android application in single account mode. This works. I want to be able to use the received access token to call an Azure AD protected API endpoint. I have both the Client app and the API registered and have granted access for the required scopes from the API into the Client app for the same.
Here is the library reference from my build.gradle
implementation "com.microsoft.identity.client:msal:2.0.8"
We're using the App Registrations experience in the Azure portal and when using the v2.0 authorize endpoint with Postman's OAuth2 token acquisition feature, it works fine. Here are my settings
Grant Type : Authorization Code (With PKCE)
Callback URL : "http://localhost"
Authorization URL : "https://login.microsoftonline.com/{{Tenant}}/oauth2/v2.0/authorize"
Token URL : "https://login.microsoftonline.com/{{Tenant}}/oauth2/v2.0/token"
Client ID & Client Secret from portal
Code Challenge Method: SHA-256
Scope : From API Registration
However, the token from MSAL Android does not have the scopes or the audience. The authentication fails and here is the log:
Authentication failed: com.microsoft.identity.client.exception.MsalDeclinedScopeException: Some or all requested scopes have been declined by the Server
This behavior seems to be fairly consistent with what would happen if the old Authorize endpoint was being used.
In any case, is there a fix/workaround for this issue, or is there something else I can do to make it work?
Edit
Here is a screenshot of the expose API blade from the API app registration
Expose API from API registration
Here is the Kotlin code I am using to sign in users
var SCOPES = arrayOf("api://<api-app-id>/<scope-name>","api://<api-app-id>/scope-name>")
PublicClientApplication.createSingleAccountPublicClientApplication(
applicationContext,
R.raw.auth_config_single_account, object : ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication) {
mSingleAccountApp = application
loadAccount() //processes the logged in account
}
override fun onError(exception: MsalException) {
}
})
loginButton.setOnClickListener(View.OnClickListener {
if (mSingleAccountApp == null) {
return@OnClickListener
}
getAuthInteractiveCallback()?.let { it1 ->
mSingleAccountApp!!.signIn(
this@MainActivity,
null,
SCOPES,
it1
)
}
})
Upvotes: 1
Views: 3346
Reputation: 7483
Not sure what custom scopes you mentioned. You need to change the scope with api://<client-id>/xxx
like this.
Please try the following code.
Acquire tokens via MSAL:
String[] SCOPES = {"api://<client-id>/.default"};
PublicClientApplication sampleApp = new PublicClientApplication(
this.getApplicationContext(),
R.raw.auth_config);
// Check if there are any accounts we can sign in silently.
// Result is in the silent callback (success or error).
sampleApp.getAccounts(new PublicClientApplication.AccountsLoadedCallback() {
@Override
public void onAccountsLoaded(final List<IAccount> accounts) {
if (accounts.isEmpty() && accounts.size() == 1) {
// TODO: Create a silent callback to catch successful or failed request.
sampleApp.acquireTokenSilentAsync(SCOPES, accounts.get(0), getAuthSilentCallback());
} else {
/* No accounts or > 1 account. */
}
}
});
[...]
// No accounts found. Interactively request a token.
// TODO: Create an interactive callback to catch successful or failed requests.
sampleApp.acquireToken(getActivity(), SCOPES, getAuthInteractiveCallback());
Call an API with access token:
RequestQueue queue = Volley.newRequestQueue(this);
JSONObject parameters = new JSONObject();
try {
parameters.put("key", "value");
} catch (Exception e) {
// Error when constructing.
}
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.
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Error.
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<>();
// Put access token in HTTP request.
headers.put("Authorization", "Bearer " + accessToken);
return headers;
}
};
request.setRetryPolicy(new DefaultRetryPolicy(
3000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
queue.add(request);
Upvotes: 0