4
votes

I have a single-page application written in TypeScript. It needs to call a middle-tier service, and the middle-tier service will, in turn, call the Microsoft Graph API.

In my SPA, I'm using the MSAL library and I log the user in by calling UserAgentApplication.loginRedirect()

I then call acquireTokenPopup and pass only one scope in the access token request parameter: the scope that is defined by my middle-tier service. Upon doing this, I see the popup but then it disappears (I never see any consent prompt). This successfully gets a token which the SPA then sends as a Bearer token on the Authorization header in an HTTPS request to the middle-tier.

The middle-tier then tries to get an OBO token by calling AAD with properties like this:

const grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
const assertion = userAccessToken;
const scope = "https://graph.microsoft.com/Calendars.Read " +
    "https://graph.microsoft.com/Calendars.Read.Shared " +
    "https://graph.microsoft.com/Calendars.ReadWrite " +
    "https://graph.microsoft.com/Calendars.ReadWrite.Shared " +
    "openid profile email offline_access";
const requestedTokenUse = "on_behalf_of";

It also has a client secret it sends along that is defined in the AAD registration for the midddle-tier. When this request is made to the AAD endpoint to retrieve an OBO token, it always fails with this error:

{"error":"invalid_grant","error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID '<ID of middle-tier application>' named '<name of middle-tier application>'. Send an interactive authorization request for this user and resource.\r\nTrace ID: 39d9bace-03f0-402b-9310-c713aa990200\r\nCorrelation ID: edfbb88f-5c63-4cef-84c1-0de8457852b7\r\nTimestamp: 2019-05-22 22:25:24Z","error_codes":[65001],"timestamp":"2019-05-22 22:25:24Z","trace_id":"39d9bace-03f0-402b-9310-c713aa990200","correlation_id":"edfbb88f-5c63-4cef-84c1-0de8457852b7","suberror":"consent_required"}

I see the bit about consent being required and how I'm supposed to send an interactive authorization request -- but I'm not sure how to do that. In fact, I thought I was doing that when I called acquireTokenPopup initially (rather than acquireTokenSilent).

If calling acquireTokenPopup in the SPA client with the middle-tier defined scope is not the correct way to send an interactive authorization request, what is?

Possibly helpful information:

  • The ID of my SPA client application is listed both in the "Authorized client applications" section of AAD registration portal for the middle-tier AND in the "knownClientApplications" section of the manifest.

Thanks!

1

1 Answers

3
votes

Firstly your usage of On-Behalf-Of flow for the scenario you describe looks good.

For the admin consent part (to get past AADSTS65001 error) you could try to do consent explicitly, in one of the two ways:

  • Use the Azure portal (works well for single tenant apps)

    Go to Azure Portal > Azure AD > App Registrations > Registration for your app > API Permissions enter image description here and then click on "Grant admin consent for [your AD]" button.

    enter image description here

  • Use the Admin consent endpoint (works for single tenant or multi-tenant apps)

    More info on Microsoft Docs here - Request Permissions from Directory Admin

    GET https://login.microsoftonline.com/{tenant}/adminconsent?
    client_id=6731de76-14a6-49ae-97bc-6eba6914391e
    &state=12345
    &redirect_uri=http://localhost/myapp/permissions
    

    Only mandatory parameter here is client_id. You can skip providing other parameters and consent should still work.

On a side note, the part that you mentioned about knownClientApplications can help with a more advanced scenario, where you're trying to club the consent for more than one applications in a single interaction. Like multiple layers of the same logical app. If that is what you're trying to achieve, then you won't really need to consent separately for middle-tier API. You could still use approach 2 for Admin Consent endpoint in this case.