Emil Morris
Emil Morris

Reputation: 11

Using MSAL on Android to get an access token to call an Azure AD protected API gives MsalDeclinedScopeException

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

Answers (1)

unknown
unknown

Reputation: 7483

Not sure what custom scopes you mentioned. You need to change the scope with api://<client-id>/xxx like this.

enter image description here

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

Related Questions