PowerApps portals and external API integration — a must-know for PowerPlatform developer
While working with PowerApps portals and delivering custom experiences to your users you may have come across a challenge of calling external APIs.
Since the portal experience mostly relies on Javascript running in the browser, which is significantly more challenging to secure than server-side, there is no obvious nor out of the box way to make a secure call to external APIs — there has to be a something that builds trust between the portal/caller and the API/callee. What works server-side e.g. API key fails fast since exposing secrets in the browser is a no-no.
Fortunately, some time ago Microsoft enabled PowerApps portal developers with a means to address this problem — OAuth 2.0 implicit grant flow.
A given portal user can acquire an ID token which can be used to authenticate and, as a next step, authorize the caller by the callee (API).
In this article I would like to:
- help you understand the security model that is given to you by Microsoft
- share considerations on production implementation of the integration
Security Model
In theory
Following OAuth 2.0 specification, for an identity within a given realm there is a way to acquire a token, so called ID token. That token is used to delegate a specific action to an external agent (in our case — Acme API) in a secure way. Once received a token, the agent can decide on whether or not to take the action based on the authenticity of the token. For a given realm, there is an Identity Provider which an identity can acquire an ID token with and which the said agent can collaborate with on the validation of the authenticity of token. More often than not, the trust between the Identity Provider and the callee relies on asymmetric cryptography (public/private key pair). It is the Identity Provider that keeps a secure hold of the private key while exposing the public part of the pair to the agent for verification.
In practice
Assuming your portal is available at https://acme.powerappsportals.com, Microsoft positions the portal as the Identity Provider and the token issuer in the security model. Every logged-in user in the portal can acquire the ID token by requesting it with the Token Endpoint available to them at: https://acme.powerappsportals.com/_services/auth/token. The simplest way to do so is to run Javascript as below:
$.get("/_services/auth/token").then(function(token) { console.log(token)
})
As an output, you will see a base64-encoded ID token similar in structure to the below:
The decoded token contains identity information such as first name, surname and the portion of metadata carrying fields (claims) such as iss, exp, nbf, iat, aud, appid, etc.
If you are not up to speed with the meaning of those claims and how to use them for token validation, I refer you to OAuth 2.0 specification or to other blogs that describe it much better than I would do here.
It is important to note that a Token Endpoint is only available to an identity that has already been authenticated and authorized by a given instance of Power Apps portal.
So now that we have the token, we can pass it to the callee e.g. in line with the Bearer scheme. How can the call validate it now?
As said before, since a Power Apps portal instance acts as an Identity Provider for its users, it should provide a way to verify the token. Microsoft publishes it at:
$.get("/_services/auth/publickey").then(function(publicKey) { console.log(publicKey)
})
for every instance of the portal. Acme Portal specific one would be available at https://acme.powerappsportals.com/_services/auth/publickey. Each instance has its own dedicated pair of public/private keys, as well. An example content should be similar in structure to the one below:
-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi4VjvPsOVIT+cQXcEnfCxEtT52NovR5kC7zyS/p9+Jduuwqmxe2Ev4Tt60sDUPYNv+gP3LXsG9xxeaR7FDTbD7QYOyO8vjlcwKcOLJQQnFXp+XVAj26YavuEMJspLMBVoa4fu9twc6yXAi25LNy7oRZ6y2cZRuwEfdIL4ezoErMAWwqFO/Dyyo5cFbwpqUXIqtrDcp0aOv4OA4GM2dUjE97M1VsdbCfP+kkp1RxDMQEyiHC4EO90ZptVDjfrbZ9rsbDNWHVZlLjoU+jk9aGOrkAzp7gwBMlL4RfNoc1S6yvMpaCqbIbYz9ZPIj8y9H3bn+3TU41wt1t7KcU0uc2wWwIDAQAB-----END PUBLIC KEY-----
Such public key is all that is needed to verify the signature of the ID token. There are many libraries available for you to do that. I recommend visiting https://jwt.io to find one that suits your needs best.
The public key endpoint is different to the Token Endpoint in that the former is publicly available. It can be used by any service that has Internet connectivity to verify the signature of the token for every request annotated with it.
Phew! We have covered quite a lot, however, that was only to build a primer. The rest of this article outlines most important considerations for production implementation of the secure integration.
Things to consider for production
#1 Everybody’s token is nobody’s token
The token retrieval has been painted quite simple. In fact, it would be too simple for it to be very useful in real life.
Please notice, the aud claim representing the notional audience of the token was empty in the previous example. All decent security guidelines around OAuth 2.0 will tell you that the callee must validate aud claim to ensure the token is meant for it to continue processing.
The good news is it is possible to configure PowerApps portals with the audience or even many audiences by populating following entries in the site configuration:
In the above table, I have defined 2 audiences acmeApi1 and acmeApi2. According to the documentation, each audience must have a corresponding RedirectUri item defined, one per RegisteredClientId item.
Now, by appending client_id query parameter to the Token Endpoint:
$.get("/_services/auth/token?client_id=acmeApi1").then(function(token) { console.log(token)
})
it is possible to acquire token that has both aud and appid set to the requested audience of the token.
#2 Tokens don’t live forever
It is worth mentioning that the ID token, once generated, remains valid for as long as it was configured by the Identity Provider. The default lifetime of the token is 15 minutes. It is hard to say if that value is low or high. The rule of thumb is though that tokens should have as short lifetime as possible because they cannot be revoked once generated. If they are intercepted by a malicious party, it may be used to call your APIs in a way that is not intended. Personally, I prefer to keep my tokens lifetime as long as it is necessary to only perform a single transaction or query, but not longer. In my case a couple seconds is usually enough. In principle, it is a trade-off between performance and security and depends on your use case. I would suggest starting from a shortest viable lifetime that makes sense to you and considering extending it should you start experiencing performance problems.
PowerApps portal allows you to modify token lifetime with ImplicitGrantFlow/TokenExpirationTime key with a up to a second resolution.
#3 Reply attacks aside
Token lifetime is an important aspect to keep security of the implemented integration in check. The short-lived nature of the token may not be considered enough, though. Short-lived tokens themselves do not prevent reply attacks. There is an auxiliary way to ensure your tokens can be used only once no matter its lifetime. PowerApps portals give you the ability to specify nonce attribute as a query parameter to the Token Endpoint. Doing so will transfer the value of nonce parameter to the ID token nonce attribute added to the token header.
{ “alg”: “RS256”, “typ”: “JWT”, “nonce”: “123456789” }
The callee can use this value to effectively rate limit the calls targeted to it based on the nonce attribute. There are still a few points to remember while implementing this protection:
- nonce should as unique as possible. My recommendation is to generate a random value with JS functions such as Math.random() and/or performance.now()
- nonce can be max 20 characters long. I would use its maximum length rather than shorter values. In principle, the longer the nonce, the better.
- rate limiting for a given audience should be keyed against nonce. There are other parameters as well to consider while building up the key driving rate-limiting policy, for example, the id of the identity.
- rate limiting at callee’s end must happen after validating the signature of token. Should it happen before, anyone could generate a payload containing the header as above with different values, which could possibly collide with the values coming off genuine tokens.
- rate-limiting window should be at least equal in length to the the lifetime of your token
Implementing the above principles along with the relatively short-lived tokens will make replay attacks ineffective — even if your tokens are intercepted (e.g. as a consequence to MITM attack), the rate limiting will prevent the malicious actor to call your API successfully — the token validity would expire before rate-limiting will allow the token to be reused.
#4 Public key is not there forever (probably)
It is a good security practice to rotate private/public key pair from time to time for many reasons. Unfortunately, at the moment Microsoft does not provide a way to safely handle key rotation scenario. What is more, Microsoft does not provide any guidelines on the policy or future around this. I imagine, similar to OpenID well-known configuration discovery mechanism could be an inspiration to PowerApps team to improve the robustness of that aspect in the future. In the meantime, the best we can do is to ensure that the callee always refers to the latest public key.
#5 (Authorization) Context is the key
As much as the ID token carries out the authentication context, in some cases it may not provide enough context to authorize the identity. All due diligence must be taken to provide necessary information to build the authorization context and properly evaluate it prior to taking any action in API — e.g. modifying data records relevant only to the authenticated identity.
#6 CORS!
PowerApps portals calling APIM will be subject to CORS and as such CORS must be enabled in the protecting policy. Check out the policy available in the existing portfolio: https://docs.microsoft.com/en-us/azure/api-management/api-management-cross-domain-policies#example-1
At last
My goal was to explain how to approach Power Apps portals integration with external APIs using OAuth 2.0 Implicit Grant flow feature provided by Microsoft. I hope you find it useful.
In the next article, I showcase the integration of PowerApps portals with Azure API Management as I believe that duo will become (if it has not already) a common pattern in the Microsoft’s product integration space.