In my last blog post, we looked at authentication and authorisation with a focus on understanding these principals when working with Microsoft Graph. In this post, we’ll dive deeper into a topic we touched on slightly in my last post, being whether to use delegated or application permissions when working with Graph. There are a number of objects where we have the option to use delegated or application permissions to authorise access, so we’ll look at which option is most suitable for different use cases.
Subscribed to Low Code Lewis?
Are you enjoying this content on Microsoft Graph? Make sure you subscribe to my blog to get all of my latest posts directly in your inbox on Microsoft 365, and Low Code
High level comparison
|Delegate Permissions||Application Permissions|
|For access on behalf of a user (user impersonation)||For access without a user present|
|Both the client (app) and the user must be authorised to make a request (have access to the resource).||Only the client has to be authorised to make the request. Requires the use of a client secret to authenticate.|
|Applicable for resources with RBAC’s (role based access systems) or where users can have access directly applied.||Suitable when data access can’t be scoped to a single user or user audience.|
To go into more detail, delegate permissions effectively allow us to call the service with the context of the user making the request, but utilising the app/client as a form of authentication.
When using delegate permissions, we have to ensure that both the app/client has permissions to the resource by granting API permissions against itself, but we also have to ensure that the user that the app is acting on behalf of has access to the data or resource.
This is why delegate permissions are available for resources where we have RBAC’s or user based access control functionality, or when it is possible to scope access to the data to a single user, or multiple users.
If we couldn’t scope access to the resource to a user, it wouldn’t be possible to assess whether the user has access to the resource preventing the client acting on behalf of the user. That is why we use delegate permissions in cases where we can scope access to the resource to the user.
When is this the better option
So you might be wondering, when is it better to use delegate permissions?
Well in pretty much any case that your resource can have access assigned to the scope of a user, I’d advise more commonly using delegate access to access a resource.
In any case that a user has to interact with your application or software for the data to need to be retrieved, you should use delegate permissions where possible. By using application permissions you’ll give your then user far too much access to data which we’ll go over in a moment.
So, to simplify…
Delegate permissions at app + user needs access + user must be present when making request -> access to data at resource
So moving on to the more ‘beefy’ one, but potentially the simpler to work with, we have application permissions. Sort of in the title, application permissions are effectively API permissions that we assign directly to the app which gives the app access to resource data without the presence of a user.
When to use application permissions
We should use app permissions when the presence of a user isn’t possible in our solution, or when the resource we’re accessing doesn’t support some time of user based access control or assigning data access to the scope of a user or multiple users.
When not to use application permissions
The main cautious point to be aware of when working with application permissions, is the data they grant access to. Because we don’t have the context of a user who might only have access to a limited scope within the resource we’re accessing, we get access to the full category of resource within the environment or tenant we’re targeting.
For example, if I grant application permissions for an app registration against users calendars and then use that to interact with Graph, it would give me access to every users calendar in my organisation, without my Exchange administrator having to provide me direct delegate access to those mailboxes, again, because we don’t have that context of the user and these permissions are for cases where we can’t provide that context.
When working with application permissions, we’re lucky that there is a safeguard in place within Azure tenants. To work with application permissions you’ll more often than not have to have a Global Administrator grant admin consent for the permissions to be used in your tenant.
When working with either delegate or application permissions, they’ll be marked with a yes or no to indicate whether a global admin has to provide admin consent for the API permissions to be used to call the APIs for accessing resources in your tenant.
A common use case and a difficulty
So, to quickly touch on a common use case which we’ll look at more closely in an upcoming blog post within this series on Microsoft Graph, you might be familiar with using the HTTP action in Power Automate to call APIs and make requests.
When working with Microsoft Graph this is exactly how we can interact with the API for cases where our requests don’t need to be repeated in other parts of our solution, or in other solutions. When we need to use requests that we’re going to repeat in places, we’ll build a custom connector.
When using the HTTP action, we have a limitation which makes working with delegate permissions slightly more tricky. The HTTP action doesn’t support caching a users credentials to pass to the API with our request along with the authentication of our application to make working with delegate permissions possible.
This isn’t impossible though but we need to work with a few more steps. By building a custom connector we can start to make working with delegate permissions possible by configuring our app slightly differently with a redirect URI, and by using this functionality which can provide user context and authentication.
We’ll look at how to take this approach in an upcoming post in this series, as well as another approach in a later post which doesn’t require creating a custom connector.