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 “caller-api
“.
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
caller API
) - a protected
caller API
, which is protected using Azure AD and scope name is “caller-api
“. - 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 caller API
.
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.
Client Secret
The 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.
Permission
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
In the 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 client secret
.
Caller API Startup
In the 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.
Startup Code
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.
Controller Code
Then 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 access_as_user
and 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.
Successful Resolution
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.