The first part of this post can be found here : https://vipulkelkar.blogspot.com/2020/03/secure-powershell-azure-function-apis.html
To secure the API we built in the previous post, we will create and configure an API management instance. Create a new one through Azure portal and import the Azure Function App in API management, some guidance provided here : https://docs.microsoft.com/en-us/azure/api-management/add-api-manually
API Management allows us to configure access restriction policies. Since we want to restrict access to the APIs based on specific roles, we will configure a Validate-JWT policy that validates the 'roles' claim in the access token: https://docs.microsoft.com/en-us/azure/api-management/api-management-access-restriction-policies#ValidateJWT
Let's configure a policy for '/Applications' endpoint. The client SPN 'MyOrg-API-Consumer' from has access to the '/Applications' endpoint. So we will validate that the access policy works when this endpoint is hit. Then we will also verify the case that 'MyOrg-API-Consumer-Devices' SPN cannot hit the '/Applications' endpoint since it only has access to '/Devices'
In the API management instance, we navigate to our API and select the /Applications endpoint. From the Inbound processing section, a new policy can be added through the 'Add Policy' button.
This opens a form to configure the policy. We can also choose to add the policies using the code editor. On the base policy which is added by default, select the code editor.
We start off by putting the validate-jwt policy. Just below the set back-end service policy, we configure the validation for the 'roles' claim in the access token received in the request
Now, from the 'TEST' tab, try to call the '/Applications' API endpoint. Make sure you have acquired an access token using the 'MyOrg-API-Consumer' SPN and add it to the header. API management allows us to setup OAuth and use the developer portal which has the ability to get access tokens : https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-oauth2To secure the API we built in the previous post, we will create and configure an API management instance. Create a new one through Azure portal and import the Azure Function App in API management, some guidance provided here : https://docs.microsoft.com/en-us/azure/api-management/add-api-manually
API Management allows us to configure access restriction policies. Since we want to restrict access to the APIs based on specific roles, we will configure a Validate-JWT policy that validates the 'roles' claim in the access token: https://docs.microsoft.com/en-us/azure/api-management/api-management-access-restriction-policies#ValidateJWT
Let's configure a policy for '/Applications' endpoint. The client SPN 'MyOrg-API-Consumer' from has access to the '/Applications' endpoint. So we will validate that the access policy works when this endpoint is hit. Then we will also verify the case that 'MyOrg-API-Consumer-Devices' SPN cannot hit the '/Applications' endpoint since it only has access to '/Devices'
In the API management instance, we navigate to our API and select the /Applications endpoint. From the Inbound processing section, a new policy can be added through the 'Add Policy' button.
This opens a form to configure the policy. We can also choose to add the policies using the code editor. On the base policy which is added by default, select the code editor.
We start off by putting the validate-jwt policy. Just below the set back-end service policy, we configure the validation for the 'roles' claim in the access token received in the request
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. You do not have the assigned role to access this endpoint.">
<required-claims>
<claim name="roles" match="any">
<value>Users</value>
</claim>
</required-claims>
</validate-jwt>
However, in this case i have manually supplied the access token in the header by fetching it through Postman
This call fails with 401 Unauthorized however we expected the call to succeed. To dig further, API Management has error handling feature which can be added to the policy code to get the details of the error and troubleshoot : https://docs.microsoft.com/en-us/azure/api-management/api-management-error-handling-policies
After error handling didn't help and weirdly the test threw a 500 Internal Server Error.
Turned out that the OpenID specification url 'openid-config url' had to be supplied to the policy for it to work. This value specifies the path to the OpenID v2.0 config file. You can view the config by opening the file in browser.
https://login.microsoftonline.com/[Tenant-ID]/v2.0/.well-known/openid-configuration
Access token and token validation concepts are detailed in reference documentation : https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens
Trying out another test failed however, this time the error details were visible since we have added the error tags in the policy.
Issuer validation failed. Issuer: 'https://sts.windows.net/[Tenant-ID]/'. Did not match: validationParameters.ValidIssuer: '' or validationParameters.ValidIssuers: 'https://login.microsoftonline.com/[Tenant-ID]/v2.0'
This is because with the OpenID config file set, the policy expects a v2.0 token coming through. The token that I had acquired through Postman was for v1.0 token endpoint.
This can be traced back to the "accessTokenAcceptedVersion" property in the app registration that registers our API. By default it is set to 'null' which means that when a token is fetched with the url of our API in audience, a v1.0 token will be issued: More details here : https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens
So we let our policy know, that we will be using v1.0 tokens. The final policy setup looks like below with the issuer check in place.
(replace the [tenant-id] with your tenant ID in the code)
The api test call works and we get a OK response.
Similarly a TEST with MyOrg-API-Consumer-Devices access token which does not have access to '/Applications' API, results is 401 Unauthorized. To make sure our policy is working, we can validate that the message we get in 401 response is the one we configured in the policy.
The direct calls to the back end Azure function API can be secured to that it accepts calls only through API management instance. Here is some documentation on how to achieve that : https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-mutual-certificates