Managing applications using Azure AD, service principals and managed identities: A permissions story
So, another year, another random blog topic change! This time we've left the world of Rx, and done a hop, skip and leap into Azure! Specifically, Azure AD, permissions and all things service principal.
As part of a recent project we needed an Azure Functions App to have access to various Azure resources, including CosmosDB and Key Vault. The set up for this went through a few different iterations (by which I mean many hours of me trying to get the permissions to all work together) until we arrived at a solution:
(Spoiler alert) We used the functions apps' MSI to authenticate to the resources, using some handy tips and tricks so that Azure AD permissions were not needed to do the set up! But more on that later, first, Azure AD? Service principals? MSIs? What are all these related-but-not the-same-identity-based things??
Service principals and AAD applications
An Azure Active Directory application is essentially an "identity" for your service. It usually resides in either the AAD tenant for the subscription in which your service was created, or the AAD tenant being used to protect the resources you wish to access. An AAD tenant (or directory) is a collection of services and users which are given permissions for resources controlled by that tenant.
Tenants can represent an entire organisation, and allow members to log into a huge range of services: Office365, Azure DevOps, Wordpress, etc. etc. Each Azure subscription resides within an AAD tenant, access to all of the resources in that subscription will be controlled by the tenant.
Both people and services authenticate via a security principal to connect to the Azure resources in a subscription. The security principals are given permissions within the associated tenant, which define what a service/user is allowed to access. For a service, the security principal is called a service principal (and for a person, it is a user principal).
This means that in order for a service to connect to resources in a subscription, it needs an associated service principal within that subscription's tenant.
So, each service is represented by an AAD application. This application has an associated service principal within each tenant it needs access to. These service principals will be used to authenticate when requesting access to resources residing in subscriptions controlled by each tenant. To allow a service to access resources within its own subscription, the AAD app will have an associated service principal in the service's home tenant.
If the service only ever needs to access resources within its own subscription then its AAD app will have just one associated service principal, which will give it access to resources controlled by the service's home tenant.
However, apps sometimes need access to resources within other AAD tenants, and in each of these other tenants it will need a different service principal.
One AAD application per app , one service principal per tenant that the app needs access to.
This is represented here, with the AAD app and service living in AAD tenant 1. The associated service principal in tenant 1 will be used to authenticate to resources within the service's own subscription. A separate associated service principal which resides in tenant 2 will be used to authenticate to resources in subscriptions 2 and 3.
Connecting a functions app via AAD using a service principal
So, in our example, the service is a functions app which is trying to access resources within its own AAD tenant. So it will need an AAD app and a service principal in order to authenticate… Lets make one!
So, using PowerShell... First, log into Azure via the AzureRM PowerShell module
command. This will set the tenant as your default AAD tenant. You can see what tenant it is currently using via the command:
If you want to change the tenant you can use the command:
The following set up assumes that the functions app and the resources that it needs access to all reside within the same AAD tenant.
So, to set up a new AAD app via PowerShell:
Once the application has been created you can retrieve the application ID using:
To create a service principal for the application, you use the command:
This will create the service principal within the current tenant. If the resources reside within a different AAD tenant, you would need to create a service principal for your app within that tenant.
The functions app can now request access to resources, authenticating as our new AAD app. To do this, it will use a connection string:
Where $TenantId
is the tenant in which the app resides.
This connection string is constructed for the given AAD application. When the functions app requests access to a resource using this connection string, it will try to access the resource using the service principal that is registered for that AAD app in the resource's AAD tenant (if there isn't one registered, the authentication will fail).
This connection string can then be set as one of the app settings for our functions app. We will call the app setting AzureServicesAuthConnectionString
.
Then, when connecting to Azure resources within the function code, the following can be done:
The token provider available as part of the Microsoft.Azure.Services.AppAuthentication NuGet package.
(The environment variables can also be obtained through using dependency injection and configuration root, however that's a tale for another time.)
The token returned here can then be used to access Azure resources that the service principal has been given access to. (WARNING: tokens expire, if you are going to go and retrieve this token every time the function runs, then it is fine to do this as above, however if you want to do this in a one-time-set-up, then it may be better to use a TokenProvider).
The point in bold is one of the main things I want to highlight. So far we have set up an AAD app for our functions app, and allowed it to make requests to resources within a tenant via a service principal. But, if the service principal in that tenant hasn't been given access to the resources, we will still get a not authorised error.
In order to assign access for the service principal, we will need the service principal object ID (which is not the same as the ID of the AAD application it represents), which can be retrieved through
However, before I go into detail about how to do that, I want to talk about Managed Identities.
Connecting a functions app via AAD using a managed identity
So an managed identity (MSI) is basically a service principal without the hassle. When you set up a functions app, you can turn on the option for an MSI.
If you click on the identity option, you will see this screen:
If the "On" option is selected, this means that an MSI has been set up for you. This managed identity is linked to your functions app, and can be used to authenticate to other Azure resources, just like a normal service principal.
If you wanted to do the same thing via an ARM template you would do the following in your functions app deployment:
The addition of the "identity" section means that the functions app will be given a system-assigned managed identity (MSI) on deployment.
Once the MSI has been set up for the functions app, using it is easy. Remember the "AzureServicesAuthConnectionString" app setting from the last section? Turns out if you just leave that blank the functions app will automatically use the connection string for it's own MSI!
So if you include this app setting but don't populate it, then the functions app will automatically try to authenticate using it's system assigned identity.
Finally, in order to assign access for this MSI, we will need to retrieve the ID. There are a couple of options for doing this.
So, the first option is by far the simplest:
However, this requires you to have AAD permissions in order to search AAD graph for the SP with the correct name (if you have AAD permissions and have no plans to do anything where you don't have them, then trust me, skip the next section).
So, the non-AAD way to do this is as follows: If you are using ARM templates to deploy the functions app, you can retrieve the ID of the MSI from the functions app, within the template. To do this you use
as one of the variables in your template, this will give you a reference to the resource. You can then use
to output the ID of the MSI from your template. If you are using the
command (I'm not going to go into detail about ARM template deployment here), then you can retrieve the deployment output using:
Where the deployment name is the name used in the original deployment, and the resource group is the resource group where that deployment took place.
If you skipped ahead, come back here!!
So, now that we have retrieved the ID for the MSI, all that we need to do now is give it (or SP if you're doing it that way) permission to access the resources…
(Note – MSIs are a relatively new addition to the world of Azure, they are not fully supported across the board yet in some situations you may need to use a full service principal!)
Assigning access to resources
For our functions app, we needed two different kinds of permissions:
Assigning role-based access
In order to assign role-based access to a resource, you will need to have Owner privileges on that resource. Once you have the required permissions you can assign roles via PowerShell. For example, to assign the role of "Contributor" on a CosmosDb account you would use:
Where $objectId
is the ID of either the service principal or MSI that you want to give access.
This will create a new role assignment within the CosmosDB account. Then, when your function app tries to perform operations within that ComsosDB account that require contributor access, it will be able to authenticate as the service principal/MSI and have the access it needs.
This approach will work for all different Azure resources, all that needs to be changed it the "ResourceType" parameter.
Adding key vault access policies
The other resource that our functions app needed access to was Key Vault. In this case access is not assigned via roles, but instead access policies are added to the vault. Via PowerShell this can be done using:
This will give the service principal/MSI with that ID get/set access to the keys in the key vault provided.
However, though not obvious, under the covers this command speaks to AAD graph to check that the ID you provided actually corresponds to a security principal. This means that in order to execute the command, you will need Azure AD permissions.
Luckily, there is a flag you can set called "BypassObjectIdValidation" which means that it does not perform this check. This is basically you saying "I know what I'm doing, just trust me and get on with it". If you set this flag, you will be able to assign key vault access policies just with the normal AzureRm permissions! (This may not sound that exciting, but it's caused me a large amount of grief this week, so to me, this is Christmas come two weeks late).
Phew… Well, that was my quick(ish) overview of AAD apps, service principals and MSIs, with some permissions related tips thrown in there! At this point our functions app is ready to go out in the world, accessing resources as it pleases (so long as they are the ones that its related service principal has specifically been given the required access to)...
Here's me and my functions app, both able to authenticate via Azure AD!
Until next time (who knows where we'll go next...)!