App service to app service auth in Azure using Managed Identity - azure

I have set up two App Services in Azure. 'Parent' and 'Child', both expose API endpoints.
Child has endpoint 'Get'.
Parent has endpoints 'Get' and 'GetChild' (which calls 'Get' on Child using HttpClient).
I want all Child endpoints to require auth via Managed Identity and AAD, and I want all Parent endpoints to allow anonymous. However in Azure I want to set the Parent App Service to have permission to call the Child App Service. Therefore Child endpoints are only accessible by using Parent endpoints (or if you have permissions on a user account to directly use Child).
In the Azure Portal:
Authentication/Authorization
I have enabled 'App Service Authentication' on both App Services.
Child is set to 'Log in with AAD'.
Parent is set to 'Allow Anonymous requests'.
Both have AAD configured under 'Authentication Providers'.
Identity
Set to 'On' for both App Services
Access control (IAM)
Child has Parent as Role Assignment, Type = "App Service or Function App" and Role = "Contributer"
With all the above setup:
Calling Child -> Get, requires me to log in
Calling Parent -> Get, returns the expected response of 200 OK
Calling Parent -> GetChild, returns "401 - You do not have permission to view this directory or page"
Without the use of Client ids/Secrets/Keys/etc, as I thought the idea behind Managed Identity was to throw that all out the window, given all the above, should Parent be able to call Child? And if so, what have I setup wrong?

Calling Parent -> GetChild, returns "401 - You do not have permission to view this directory or page"
Without the use of Client ids/Secrets/Keys/etc, as I thought the idea
behind Managed Identity was to throw that all out the window, given
all the above, should Parent be able to call Child? And if so, what
have I setup wrong?
There are two things that I notice with current setup.
1. Acquire a token using Managed Identity to call "Child" service endpoint from "Parent"
Managed Identity only provides your app service with an identity (without the hassle of governing/maintaining application secrets or keys). This identiy can then be used to acquire tokens for different Azure Resources.
But it is still your App's responsibility to make use of this identity and acquire a token for relevant resource. In this case the relevant resource will be your "Child" API. I think this is probably the part you are missing right now.
Relevant documentation on Microsoft Docs - How to use managed identities for App Service and Azure Functions > Obtain tokens for Azure resources
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net");
// change this to use identifierUri for your child app service.
// I have used the default value but in case you've used a different value, find it by going to Azure AD applications > your app registration > manifest
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://<yourchildappservice>.azurewebsites.net");
This C#/.NET sample uses Microsoft.Azure.Services.AppAuthentication nuget package and acquires a token for Azure Key Vault. In your case, you will replace https://vault.azure.net with the identifierUri for your "Child" service. It's usually set to https://<yourappservicename>.azurewebsites.net by default, but you can find it's value by going to Azure AD applications and then finding the relevant app registration > manifest. You could also use applicationId for the target application (i.e. "Child") to acquire the token.
In case you're not using C#/.NET, same Microsoft Docs link above also has guidance on how to acuqire token using Managed Identity and REST based calls from any platform. Using REST Protocol
Here is a blog post that also gives a good walk through - Call Azure AD protected website using Managed Service Identity (MSI)
2. Azure RBAC Role Assignments are different from Azure AD roles that you may want to use
I see that you have assigned contributor role to Parent App Service's identity from IAM. This role assignment works for Azure RBAC and help in giving permissions for managing the resources, but Azure AD role claims work differently.
If what you were looking to do is to assign a role to parent app, which can be checked in child app and only then allow the calls there is a different way of setting this up.
I should first mention that this role based setup is for a little advanced scenario and not really mandatory to do. You should be able to call "Child" service from "Parent" once you follow the steps in point 1 described above.
Now once the call from Parent to Child is working, you may want to limit the access to Child app service to only "Parent" or a few valid applications. Here are two approaches to achieve that.
Both the approaches are explained on Microsoft Docs here - Microsoft identity platform and the OAuth 2.0 client credentials flow
Relate SO Posts and Blog
Is there a way to secure an Azure Function that will only be called from a specific Azure Logic App?
Azure Active Directory - How to restrict Backend API App Registration to a specific client App Registration
https://joonasw.net/view/calling-your-apis-with-aad-msi-using-app-permissions
Approach 1 - Use Access Control Lists
When your "Child" API receives a token, it can decode the token and extract the client's application ID from the appid and iss claims. Then it compares the application against an access control list (ACL) that it maintains.
Depending on your requirement, API might grant only a subset of full permissions or all permissions to a specific client.
Approach 2 - Use Application Permissions or Roles
Configure your child API application to expose a set of application permissions (or roles).
This approach is a little more declarative, as you define an application permission that needs to be assigned to any application that can call your child-api.
Navigate to Azure Active Directory > App Registrations > App registration for your child-api app > Manifest
Add a new application role.. using json like this:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Can invoke my API",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Apps that have this role have the ability to invoke my child API",
"value": "MyAPIValidClient"
}]
Assign the app permission to your frontend app
New-AzureADServiceAppRoleAssignment -ObjectId <parentApp.ObjectId> -PrincipalId <parentApp.ObjectId> -Id "fc803414-3c61-4ebc-a5e5-cd1675c14bbb" -ResourceId <childApp.ObjectId>
Now, in the auth token received by your child api, you can check that the role claims collection must contain a role named "MyAPIValidClient" otherwise you can reject the call with Unauthorized exception.

To expand on the accepted answer.
You need to define an "App Role" in the target app registration's manifest. This is the app registration which is used to represent the resource (API App Service).
Then you use the Azure CLI to grant permission for that "App Role" to the Enterprise App (The one generated when you setup a managed identity for the client app). See the "APIs and other Azure AD registered applications" in this article for detailed steps https://blog.yannickreekmans.be/secretless-applications-add-permissions-to-a-managed-identity/
You can retrieve the token using the following once the permissions have been granted. The code snippet below uses Azure.Identity which is now the recommended library for Managed Identity in Azure.
public class AzureAdTokenRetriever : IAzureAdTokenRetriever
{
private readonly ILogger<AzureAdTokenRetriever> logger;
private readonly IMemoryCache inMemoryCache;
public AzureAdTokenRetriever(
ILogger<AzureAdTokenRetriever> logger,
IMemoryCache inMemoryCache)
{
this.logger = logger;
this.inMemoryCache = inMemoryCache;
}
public async Task<string> GetTokenAsync(string resourceId, string scope = "/.default")
{
var resourceIdentifier = resourceId + scope;
if (inMemoryCache.TryGetValue(resourceIdentifier, out var token))
{
this.logger.LogDebug("Token for {ResourceId} and {Scope} were fetched from cache", resourceId, scope);
return (string)token;
}
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(new [] { resourceIdentifier }), CancellationToken.None)
.ConfigureAwait(false);
// Set cache options with expiration 5 minutes before the token expires
var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(accessToken.ExpiresOn.AddMinutes(-5));
inMemoryCache.Set(resourceIdentifier, accessToken.Token, cacheEntryOptions);
this.logger.LogDebug("Token for {ResourceId} and {Scope} saved in cache with expiration of {TokenExpiry}",
resourceId, scope, cacheEntryOptions.AbsoluteExpiration);
return accessToken.Token;
}
}

Related

Get 401 Unauthorised calling WebApi from another WebAp on behalf of api (not user)

We have a number of ASPNET Core Web Apis in Azure that we call on behalf of a User. That user has normally signed into an ASPNET Web Site, also in Azure.
We are introducing an Audit Service. That feels like it should be called on behalf of the calling service rather that the authenticated user.
The Audit Service has an associated App Registration in Azure AD
The Audit Service has a scope called "access_as_application" although having seen documentation about a ".default" scope I wasn't sure that i needed a scope
The calling application (ASPNET Core Web Site) has been added in the "Authorized client applications" section against the previously mentioned scope
In the calling application I am getting an access token for the app rather than the user by using GetAccessTokenForAppAsync.
var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
System.Diagnostics.Debug.WriteLine($"access token-{accessToken}");
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Currently I am running the calling application and the audit service on my local development machine.
When I make the call to the audit service I am getting a 401 Unauthorized
var response = await this.httpClient.PostAsync($"{this.auditApiBaseAddress}v1/entries", requestContent);
UPDATE
I have added the Azure Ad App Id of the calling application as a knownClientApplication on the Audit Service, via the App Manifest. That did not prevent the 401
"knownClientApplications": [
"7ac7f49d-e9fa-4e1b-95b2-03e0e1981f58"
],
UPDATE 2
I can see that the instance of the service running in Visual Studio is reporting a stack trace. It is referring to a IDW10201 issue.
System.UnauthorizedAccessException: IDW10201: Neither scope or roles claim was found in the bearer token.
at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilderExtensions.<>c__DisplayClass3_1.<<AddMicrosoftIdentityWebApiImplementation>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilder.<>c__DisplayClass14_0.<<CallsWebApiImplementation>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Any thoughts why?
You should currently be performing server-to-server interaction, that is, no user involvement. So your server application needs to create an appRole, and then grant the app Role as an application permission to the client application.
First, you need to expose the api of the server application protected by Azure, which can be configured according to the following process:
Azure portal>App registrations>Expose an API>Add a scope>Add a client application
Then you need to create the appRole of the server application, and then grant that role as an application permission to the client application.
Next, go to client application>API permissions>Add a permission>My APIs>your api application.
Finally, you need to obtain an access token using the client credential flow where no user is logged in:
Parse the token:
Whilst I've marked Carl Zhao's contribution as the answer I found the screenshots a bit hard to follow so this is my attempt at making that a bit clearer.
In this scenario where we want authentication between Azure Ad registered application (client) and another Azure Ad registered application (Audit Service) scopes were not the solution. Rather than exposing a scope we needed to expose an appRole.
The steps required to expose and then request access to the app role were
App Registrations -> Audit Service -> Manage -> App roles -> Create app role
When creating the app role ensure the Allowed member type is "Applications"
Now go to App Registrations -> YourClientApplication -> Api permissions -> Add a permission
I expected the Audit Service to appear under "My APIs" in the "Request API permissions panel". I did not, the only way I could request permisison to the previously created AppRole was to enter the AppId of the Audit Service in the search box under "APIs my organization uses"
Once I was able to select the audit service I selected "Application permissions" rather than "Delegated permissions" and then I selected the specific role
Once the client application had been granted access we needed to write code get to an access token. Using Mictosoft.Identity.Web library
var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
System.Diagnostics.Debug.WriteLine($"access token-{accessToken}");
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Note the call to GetAccessTokenForAppAsync not GetAccessTokenForUserAsync. GetAccessTokenForAppAsync still requires a scope however as already stated a custom scope is not needed. The scope is ".default" so the string passed to that call in our case was https://ourdomain/audit-service/.default" which is our App ID URI plus ".default"

Unable to get access token. 'AADSTS500011: The resource principal named 'xxx' was not found in the tenant -tenantid

I am trying to get the access token for the Azure function app. I have enabled managed identity for the function app(system assigned). but while fetching the token using the nuget Azure.Identity.
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { "https://xxx.azure-api.net/" + "/.default" }) { }
);
I am getting the error.
The resource principal named 'xxx.azure-api.net' was not found in
the tenant 123
but when run az cli to check the subscription details, the subscription indeed part of the tenant 123 only.
Here is what I have finally done.
I have registered an App in AD. and Exposed the API of that App.
I have assigned System Assigned Managed Identity to the Function.
In the local I am not able to request token because Azure CLI is not given consent.
After deploying the application in Function my Function app can request a token using its identity.
You need to register the application in azure ad and enable the access token. Once that is done the you need to provide RBAC access to your xxx.azurewebsites.net
Follow this article for the step by step documentation Microsoft Document Reference
Unfortunately, the error message is not really helpful. But adding a scope to the app registration solved the problem for me:
In Azure Portal navigate to App Registrations
Find your app, in the left side menu select Manage => Expose an API
Add a scope. I named mine api_access as this was where this error occurred.
In my case I then got an API URI (like api://client-id/scope_name) which I used in my Angular app. Error message was gone.
Also, make sure that in the Enterprise Application you have created, under Manage => Properties, "Assignment required" and "Visible to users" is turned on.

How can I allow a service account to access my REST API when using Roles-based Authorization in Azure?

Summary: I have a REST API that I use for functional testing. I only allow people or groups in a specific "Tester" role to hit the API. I want to trigger this functional testing during an Azure DevOps Release Pipeline automatically, but I can't figure out how to authorize the machine account to hit the API.
Details:
I have this API secured with [Authorize(Roles = "Tester")]
[Route("quotas/developers")]
[HttpGet]
[Authorize(Roles = "Tester")]
[SwaggerResponse(HttpStatusCode.OK, "Successful operation", Type = typeof(DeveloperQuota[]))]
public async Task<List<DeveloperQuota>> GetDeveloperQuota([FromUri]string developerUpn)
To set this up, I have an Enterprise Application registered in Azure Active Directory. In the manifest, I declare the role.
And then in the Enterprise Application I add some users and groups which are assigned the role "Tester."
This works fine for running my functional tests by hand. I run the tests, it pops up an oauth dialog for me to enter my credentials, it grabs my Bearer token from the successful auth request then passes it along to the APIs.
private string GetActiveDirectoryToken()
{
string authority = this.configuration.ActiveDirectoryAuthority;
string resource = this.configuration.ActiveDirectoryAudience;
string keyVaultUri = this.configuration.KeyVaultUri;
IKeyVaultAdapterFactory keyVaultAdapterFactory = new KeyVaultAdapterFactory();
var keyVaultAdapter = keyVaultAdapterFactory.CreateInstance(KeyVaultServicePrincipal.PowerShellAppId);
SecureString clientIdSecure = keyVaultAdapter.GetAzureKeyVaultSecretSecure(keyVaultUri, "GasCallbackRegistrationClientID", null).Result;
SecureString redirectUriSecure = keyVaultAdapter.GetAzureKeyVaultSecretSecure(keyVaultUri, "GasCallbackRegistrationClientIDRedirectUri", null).Result;
var authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(
resource,
SecureStringUtilities.DecryptSecureString(clientIdSecure),
new Uri(SecureStringUtilities.DecryptSecureString(redirectUriSecure)),
new PlatformParameters(PromptBehavior.Auto)).Result;
return result.AccessToken;
}
Of course, if I'm running this during automation, there will be nothing to fill in the dialog with creds, nor do I want to be storing a copy of these creds, especially since these creds roll on a schedule which are maintained elsewhere.
My thought was that I could create an Azure Service Principal, associate a cert with the service principal, install the cert on my deployment machine, login as the Service Principal if the cert was available, and then put that Service Principal in the "Tester" role. The problem is I can't add a Service Principal as a user in the Enterprise Application. It only appears to allow me to add "Users and groups." I similarly can't add a service principal to a group.
Any thoughts on how I can authorize my deployment box to hit these APIs?
Roles published for applications are treated as application permissions and not assignable to other apps via the "Users and Groups" assignment screen.
To assign the app permissions to a client app, go to the client app's registration page, click on Api Permissions and then Add a Permission. Select the My Api tab, search for your application that published the app roles and you'd see the app role listed in the list below. Select that, save and then grant admin consent.

Azure AD - Add app principal to a Group

I have an Azure AD app (AAD App1) which has user assignment enabled. So only, users from a particular group let's say "Group A" can access any resource (let's say an Azure Function API) protected by that Azure AD app.
Now I have another daemon Azure function job, which needs to make an authenticated call to the above mentioned Azure function API. Since this is a daemon job, I have generated another Azure AD app (AAD App2) for this.
Below is my code to get access tokens:
string resourceId = "id of app used to authenticate azure function"; // AAD app ID used by the Azure function for authentication
string clientId = "id of app registered for the daemon job";// AAD app ID of your console app
string clientSecret = "secret of app registered for the daemon job"; // Client secret of the AAD app registered for console app
string resourceUrl = "https://blahblah.azurewebsites.net/api/events";
string domain = "<mytenant>.onmicrosoft.com"; //Tenant domain
var accessToken = await TokenHelper.GetAppOnlyAccessToken(domain, resourceId, clientId, clientSecret);
Now when I try to generate access token to access the Azure function API, I get an invalid grant error as below:
AdalException:
{"error":"invalid_grant","error_description":"AADSTS50105: Application
'' is not assigned to a role for the application
''.\r\nTrace ID:
6df90cf440-c16d-480e-8daf-2349ddef3800\r\nCorrelation ID:
4c4bf7bf-2140-4e01-93e3-b85d1ddfc09d4d\r\nTimestamp: 2018-05-09
17:28:11Z","error_codes":[50105],"timestamp":"2018-05-09
17:28:11Z","trace_id":"690cf440-c16d-480e-8daf-2349ddef3800","correlation_id":"4c4bf7bf-2140-4e01-93ef3-b85d1dc09d4d"}:
Unknown error
I am able to properly generate AAD access tokens if I disable the user assignment.
I am trying to avoid creating a service account here. Is there anyway I can add an app principal to an Azure AD group or add it as a member of another Azure AD app?
Unfortunately, you cannot add an AAD application/service principal as a member of Azure AD group.
I have confirmed this issue in My Answer for another similar question [EDIT - now seems to be possible, see said answer]
You can also upvote this idea in our Feedback Forum. Azure AD Team will review it.
Hope this helps!

Azure AD token has already access to other app without permissions

We have 2 apps registered in Azure AD, let's call them WebApi1 and WebApi2.
WebApi1 needs to call WebApi2. A secret has been configured in WebApi1 in order to get a token. Here is the code I'm using to get the token and then make the call to WebApi2:
And here is how my WebApi2 is configured:
The thing that I don't understand is that I would expect WebApi2 to return a 401 exception since I have not set any permissions in Azure (via the App Registration portal) to WebApi1.
Yet, the call is made successfully and WebApi1 has access to WebApi2.
Why WebApi1 has access to WebApi2 without the use of permissions in Azure?
Your web api application should check access using the IsInRole() or the [Authorize] attribute. If your web api doesn't check access , by default the access token with no application roles(permission) could access to your web api .
Please refer to document Roles based access control in cloud applications using Azure AD . Since you are acquiring token with application identity (client credential flow) , please check the Assigning client applications to application roles of resource APIs section in the document .
Just another thing.
If you're working with Azure and roles, when setting the WindowsAzureActiveDirectoryBearerAuthenticationOptions, you'll need to set the right role type in order for IsInRole (or Authorize("YourRole")) to work.
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
ValidAudience = ConfigurationManager.AppSettings["AzureAd:Audience"],
RoleClaimType = System.Security.Claims.ClaimTypes.Role
},
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
Tenant = ConfigurationManager.AppSettings["AzureAd:Tenant"],
});

Resources