Search code examples
asp.net-authorizationmicrosoft-identity-web

Acquire multiple scopes at once - ASP.NET Core MVC


I'm building an ASP.NET Core 6 MVC app and using the Microsoft.Identity.Web package for authentication and authorization.

How can my application acquire multiple scopes at once when a user from a new tenant logs in for the first time?

My app will be multi-tenant, and I want to acquire multiple scopes when a user from a new tenant logs in. I want to acquire the scopes up front (and not incrementally) because a frequent use case is that:

  1. An admin user from a new tenant logs in and grants consent for the required scopes on behalf of their organisation

  2. A non-admin user from the same tenant logs in afterwards. This user is not allowed to grant consent, and thus relies on the admin user to have granted consent up front.

I was initially using the [AuthorizeForScopes] attribute on my home controller to ensure that the relevant scopes were acquired. This works fine when acquiring a single scope at a time, but when I attempt to acquire multiple scopes at once it seems like the client (browser) goes into and endless loop. It's like it doesn't know which scope to ask for first.

I was expecting that my application would simply ask the user to grant consent to all the specified scopes at once.

Specifically I'm asking for these scopes:

  • https://graph.microsoft.com/v1.0/user.read
  • https://management.core.windows.net/user_impersonation
  • https://database.windows.net/user_impersonation

Solution

  • We ended up implementing our own attribute like this:

    public class AuthorizeAllScopesAttribute : AuthorizeForScopesAttribute
    {
        public AuthorizeAllScopesAttribute()
        {
            Scopes = new[]
            {
                "https://graph.microsoft.com/user.read",
                "https://database.windows.net/user_impersonation",
                "https://management.azure.com/user_impersonation"
            };
        }
    }
    

    This seems to allow us to request consent for multiple scopes at once. We use it on controllers like this:

    [AuthorizeAllScopes]
    public class HomeController : Controller
    {
        ...
    }
    

    However, this led to an issue when using the Microsoft.Graph.GraphServiceClient. We are using this client to talk to the Graph API. Apparently it attempts to use all of the requested scopes when sending requests, rather than just using the ones relevant to the Graph API. This makes all requests fail because some of our scopes are invalid to the Graph API.

    To fix this we had to explicitly register Microsoft Graph with the desired scope(s) in our Startup.ConfigureServices method:

        services
            ...
            .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "user.read" })
            .AddMicrosoftGraph("user.read")