Authorizing PowerApps portal calls with Azure API Management
In the previous story I tried to explain how to make a good use of OAuth 2.0 Implicit Grant flow feature while reaching out to external APIs from PowerApps portals (PAPs).
In this article I would like to showcase how to offload application-to-application authorization with Azure API Management (APIM) which, in many Azure workloads in the world, forefronts backend APIs.
Azure API Management is a flexible API Management solution that can be flexed to our needs by applying a hierarchical policy model for processing pipeline involving inbound, backend and outbound stages. Please find more information here .
What I would like to focus on is the inbound stage of it — a set of instructions that execute before the actual downstream processing happens.
The token
Recently, we have learnt how to acquire an ID Token. The ID Token is actually a JWT token which must be validated before executing some action delegated to the API by the caller.
What’s on the menu?
It is always good to start with something that is already there and tested. APIM provides you with <validate-jwt> policy that takes off majority of the effort related to JWT token validation. Given we know what PowerApps portals provide, here is the list of configuration behaviours we would expect from APIM:
- ability to accept JWT tokens in line with Bearer scheme
- ability to define the audience
- ability to point to the public key so that signature can be validated
- ability to define the issuer
- ability to validate exp, nbf, iat claims
Here is my first attempt to construct <validate-jwt> policy (I’ve skipped non-essential attributes):
It does not look complicated. The policy is configured to satisfy all the criteria points.
The was the good.
The bad is there is no way to tell the policy to read the public key from external resource. If MS exposed public keys wrapped with OpenID Well Known configuration we could use <openid-configuration> instruction to point the policy to the key and it would work smoothly. I hope at some point MS delivers that for compatibility reason.
The ugly is the above simply does not work. At the moment APIM does not tolerate public key configured as the above — it is to do with handling RSA keys and underlying implementation. Here is the sample of error message you get when just using public keys as they are:
"JWT Validation Failed: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: '1H_BpPlAsDnjxjhUPDW-9-0m_h7SPjgYPXAAb7J5mMU'. , KeyId: \r\n'.\nExceptions caught:\n 'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.\nAlgorithm: 'RS256'
Happily there is a workaround to it. The policy accepts an alternative configuration setting for public keys:
<issuer-signing-keys>
<key n="{modulus goes here}" e="{exponent goes here}" /></issuer-signing-keys>
Instead of just pasting public key in its base64-encoded form, it has to be broken down into modulus and exponent of the public key. This is the only way to get APIM to work with public keys at the moment.
Disassembling public keys
Once you get this process automated, it is not a problem anymore. The work just needs done, so let’s do it. Here is a python script to break the public key into 2 required components:
It requires 2 dependencies cryptography and pycrypto. You can easily get them installed with pip. Once ready, run:
python public_key_digestion.py < pub.key
so the output is something like:
Modulus:
i4VjvPsOVIT+cQXcEnfCxEtT52NovR5kC7zyS/p9+Jduuwqmxe2Ev4Tt60sDUPYNv+gP3LXsG9xxeaR7FDTbD7QYOyO8vjlcwKcOLJQQnFXp+XVAj26YavuEMJspLMBVoa4fu9twc6yXAi25LNy7oRZ6y2cZRuwEfdIL4ezoErMAWwqFO/Dyyo5cFbwpqUXIqtrDcp0aOv4OA4GM2dUjE97M1VsdbCfP+kkp1RxDMQEyiHC4EO90ZptVDjfrbZ9rsbDNWHVZlLjoU+jk9aGOrkAzp7gwBMlL4RfNoc1S6yvMpaCqbIbYz9ZPIj8y9H3bn+3TU41wt1t7KcU0uc2wWw==
Exponent:
AQAB
as a result.
Working policy with the public key
Now it is as easy as adding the above to the policy and voilla!
Dessert: rate-limiting with nonce
To prevent reply attacks nonce attribute of the token can be used to ensure a request can be handled once and once only. The addition to the existing policy would be
This snippet must be added right after <validate-jwt> policy to ensure the token has been validated. I appreciate the snippet is a bit rough around the edges but it reflects the intention adequately:
- nonce value is extracted out of the token (I thought I could rend it out of Jwt object but I failed thus the workaround with manual parsing of the JWT header)
- sub claim is extracted from pre-parsed token (check jwt-validate policy attribute output-token-variable-name) to shape the rate limiting key sub+nonce, which effectively prevents a concrete identity to use the token more than once in a 60 second period (I assume your token expiry period is no longer than this)
As a result, on the subsequent use of token within its window of applicability, the caller receives 429 Too Many Requests.
Job done
That is it — we have arrived at the end of the APIM and PAP integration gig. I hope you find this useful!