Many organizations are now moving to Microservices and APIs calling other APIs is very common in Microservices architecture.
In this article, let’s see how a secured API can call another secured API, both secured using Azure AD.
Protected Web App and API
The Web App (.NET Core Web Application) will call the web API, both are protected using Azure AD. Let’s call this API as
caller API in Azure
app registration and while registering this API’s scope, name the scope as “
Refer my previous blog for creating and configuring these two projects.
Create New Web API project
Create two web API projects using Visual Studio. Register both of them in Azure AD and get client id and tenant id. Configure the middleware and make both APIs secure.
Refer my previous blog article about securing web APIs for detailed steps.
Complete Applications Setup
After following all above steps, we should have
- a protected web application that calls one web API (let’s say
- a protected
caller API, which is protected using Azure AD and scope name is “
- one more protected API project, which is also protected using Azure AD, but nobody calls it yet and the scope name is “access_as_user”
We will try to call this new API, from the
Azure AD App Registration
Now, let’s login to Azure Portal and go to the
Caller API registration. We need to configure two more things in
caller API app registration.
caller API is calling another API, so it becomes confidential client application. So, we need to go to
caller API's app registration and generate the client secret.
Copy this client secret and keep it at some place safe, we will need to add it to
caller API's configuration file.
Next, thing to add the permission to call the new API. From API Permissions in left navigation, select Add a permission and then select My APIs to see your newly registered API. Add this permission so that
caller API can call the new API.
Caller API Configurations
caller API configurations, we need to add the client secret. Below is the complete configuration file in my sampler
caller API project. Just do not forget to update the
Caller API Startup
caller API, we need to ensure that the token of calling user is cached and used for the other API call. There are two main things which are required to be done here.
In startup of
caller API, we need to add middleware for caching the incoming tokens. In Microsoft.Identity.Web 0.1.3 package, we can find an extension method,
AddProtectedWebApiCallsProtectedWebApi, which abstracts all the details of validating token and adding it to cache.
Below snippet shows the completed
startup.cs file and all middlewares are added there.
ITokenAcquisition instance can be used to get the token. This token can be added to Authorization header as bearer token and then we can call the new API.
Below snippet shows the code where
ITokenAcquisition instance is injected in the controller. Then the same instance is used to get the cached token and injected in the HTTP request to the new API.
Web App Modifications
Initially, I assumed that it is logical to specify both the APIs scope in the web app, so that the token we get will be applicable for both the APIs.
So, I specified both
caller-api scopes as shown below in startup.
Also, on the button click, added below code to call the caller-api, which also asked for scopes of both the APIs.
The Unexpected Error
Then when I tried to run the application, it threw an error. I was surprised to know that the scopes of both APIs can not be specified together.
MsalServiceException: AADSTS28000: Provided value for the input parameter scope is not valid because it contains more than one resource.
I could not find why the error was occurring from the documentation. But the error was stating that I have specified two resources and the scopes corresponding to our two APIs.
So, I thought may be two API scopes are not allowed in the initial scopes. But if they are not allowed in initial scopes requested for login, and if we just specify any one API scope, the token acquired will not have permissions to call the other API.
Failed Attempt To Resolve
To solve this issue, I thought I will remove the permission from startup file (from the snippet provided above) , but will keep both the permissions in the code in web app that calls the caller-api.
But this did not resolve the error. I was able to login successfully, and when I tried to call the API, I got the same error.
I went back to Azure
Caller API registration and provided admin consent for default directory by clicking on the Grand admin consent for default directory button in caller-api’s app registration -> API Permissions screen.
I was sure that if I do this, it will provide the caller-api permission to call the other API, even though the user token has not requested that permission.
Updated startup file and the code that calls
caller API, to only ask for the
caller API scope.
The code which calls API from the web application was also updated as below:
Here the theory was if I get token which allows permission to call the
caller api, then at least I will be able to login to the web application and will be able to issue call to it. The
caller API would still be able to call
Run and Verify
I was successfully able to login in the web application and call the API which calls other API.
Did you find any other resolution instead of providing admin consent? Let me know your thoughts.