Some of you may have been developing an application that integrates with Azure AD, and hit this screen:

Screenshot of the error in Azure AD

In the tiny text at the bottom you can find this error code:

AADSTS90094: The grant requires admin permission.

Note that this article is based on Azure AD v1. For v2/converged apps, I recommend Marc LaFleur's article: http://massivescale.com/microsoft-v2-endpoint-admin-consent/.

Why this happens

TL;DR, your app requires a permission which requires an administrator to consent to it, but it has not been done and the currently signing in user is not an admin.

If you want to skip explanations, you can find guidance on how to fix it at the bottom of the article.

But it is also good to know the details on what Azure AD is expecting to exist so you can actually figure out why this error occurs.

So here we will go through how to find out manually if the permissions have been granted successfully.

Delegated permissions requiring admin consent

Let's say your application requires a delegated permission which requires an admin to consent, like Read all users' full profiles on the MS Graph API here:

Permissions list for MS Graph API

Now when a user tries to authenticate, Azure AD is looking for an OAuth2PermissionGrant object on the service principal.

You can find this object by using the Azure AD Graph Explorer, and after logging in, enter a URL like this:

https://graph.windows.net/joonasapps.onmicrosoft.com/servicePrincipals/6cef654b-d2eb-4dd1-95c0-8032b29a963e/oauth2PermissionGrants

Obviously replace values to match your app and directory.

Replace joonasapps.onmicrosoft.com with your Azure AD tenant's id, or any of its verified domains.

You can find the service principal id by finding your app registration in Azure Portal, then click the link that says Managed application in local directory above it. Then, go to Properties. There you can find the Object ID.

You can also find the service principal from the Enterprise applications tab.

Now in order for Azure AD to consider the permissions granted, this object must match the following requirements:

  1. consentType should be either AllPrincipals or Principal
    • If it is Principal, then principalId must equal the logging in user's objectId
    • AllPrincipals means an administrator has granted consent and thus no user needs to be asked anymore
  2. scope should contain all of the delegated permissions required by the application

If this object is not found, consent will be asked from the user. And then if the user is not an admin, but the permission requires an admin to grant, you get the error.

A bit of a "funny" thing happens if the object is found, but the scopes do not match.

If you did not explicitly ask the user to consent with prompt=consent, the user authenticates successfully. However, the resulting access token will only contain the scopes that were previously consented. Something to consider when designing your authentication flow.

If you do then redirect them to Azure AD again with prompt=consent, you get the same consent check as before if the object was not found at all.

Application permissions

App permissions differ from delegated permissions in that they require an administrator to consent always.

They are essentially roles which can be applied to service principals. So if the role has not been granted, the permission is not granted.

Let's say our app now requires an app permission like Send mail as any user on the MS Graph API here:

Permissions list for MS Graph API

This time, Azure AD is looking for an appRoleAssignment on the service principal.

You can find your app's granted app permissions by using the Azure AD Graph Explorer, and after logging in, enter a URL like this:

https://graph.windows.net/joonasapps.onmicrosoft.com/servicePrincipals/6cef654b-d2eb-4dd1-95c0-8032b29a963e/appRoleAssignments

Like above, you must input your own tenant's and service principal's identifiers.

In order for Azure AD to consider the permission granted, the object must match these requirements:

  1. principalId matches the service principal's objectId
  2. id matches the id of the app permission being requested
    • These are defined on the service principal identified by resourceId
  3. resourceId matches the API's service principal's objectId

If this object is not found, and the current user is not admin, you get the error.

Note: if you add more app permissions after granting some, there will be no error, unless you explicitly ask for consent. Same as delegated permissions.

If you need to know which id corresponds to what permission, you can get them once again with the Graph Explorer:

https://graph.windows.net/joonasapps.onmicrosoft.com/servicePrincipals/ea63ed19-7427-4282-b2a3-eb8f049eaed5

In my test directory that is the object id for Microsoft Graph API's service principal. You can find all of the delegated permissions (oauth2Permissions) and app permissions (appRoles with allowedMemberTypes including Application) there.

One small curiosity you can discover there is that MS Graph is also known as Microsoft.Azure.AgregatorService. The typo with "Agregator" aside, it reflects MS Graph's position as a gateway that aggregates other APIs. I thought that was interesting :)

How to fix it

Now, how can we go about creating those required objects?

Firstly, each of these options requires the user to be an administrator.

One of the easiest ways in a single-tenant scenario is to click the Grant permissions button found here:

Grant permissions button

If you hit the error on authentication, there is also an option to log out and sign in as an administrator:

Option to log out and sign back in as admin

In the case where you added permissions later after consents were already granted, you can force consent again by adding prompt=admin_consent to the authorize URL. Then an administrator can sign in and consent to the newly required permissions.

Why not prompt=consent? Because then the admin would only grant required delegated permissions for themselves :) App permissions will be granted correctly with either prompt.

Example authorize URL:

https://login.microsoftonline.com/joonasapps.onmicrosoft.com/oauth2/authorize?response_type=code&client_id=0808d6a7-164c-41be-8930-8dad927e42ae&redirect_uri=https%3A%2F%2Flocalhost%3A44307%2F&prompt=admin_consent

If this goes successfully, you can use the methods described in the earlier section to confirm this has indeed happened with Azure AD Graph Explorer.

Multi-tenant scenarios

If this is the first time a user from another tenant is logging in, and your app requires permissions which need administor consent, the first user who signs in must be an admin.

You should have some kind of onboarding flow in which you include prompt=admin_consent in the authorize URL as above. Though this time you would use common instead of a tenant id/domain:

https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=0808d6a7-164c-41be-8930-8dad927e42ae&redirect_uri=https%3A%2F%2Flocalhost%3A44307%2F&prompt=admin_consent

If another tenant has previously consented to permissions, but you have now changed them, that is a little harder.

In this case you cannot just force consent for any user signing in. They will be unable to login at all to your app if they are not administrators.

What you need to do is:

  1. Check the access token you get from Azure AD. If it does not contain the scopes/roles expected, then new consent is needed
    • Hopefully your app can still work without those new permissions :)
    • When you ask for a token with the Authorization Code Grant flow, the response includes what scopes are in the token, e.g. "scope": "Calendars.ReadWrite User.Read"
    • Alternatively you can try calling the API with the access token. If it comes back with a 401/403, then we probably do not have the permissions granted yet.
  2. An email/notification might be needed that says something like An administrator from your organization needs to sign in through this link to enable the new features blah blah...
    • The link would be the authorize URL containing prompt=admin_consent