Azure AD, Multi-tenant, App Roles Assignment for users from another tenant - azure

I'm working on web application that contains client side (SPA, angular 9) and backend (WebAPI, ASP.NET Core 3.0). Decided to use Application Roles feature to authorize users in our application. And i have requirement to be able to manage Application role assignments for users from our application UI via MSFT Graph API.
I registered MyAuthApp application in Azure AD TenantA. And created several App Roles there.
Authentication works fine. Client side gets token and attaches it to http requests to backend. Authorization also works fine i can extract app roles from the token and validate them.
Problem with adding Application role assignments for users from other AzureAD tenant -- TenantB. Seems that problem in GraphServiceClient configuration due to GraphApiAuth registered in TenantA.
Question: is this possible to add application role assignment for user from TenantB using GraphServiceClient authorized by Client Credentials in TenantA?
Right now when i do add role assignment i'm getting exception like resource with some Guid not found. This resource is a user (from TenantB).
This is a piece of code that adds user app role assignment. I see possible problem in GetGraphServiceClient function. It uses as authority URL with TenantA Id.
public async Task<AppRoleAssignment> AssignAppRoleToUser(Guid userId, Guid appRoleId)
{
var graphClient = await this.graphClientProvider.GetGraphServiceClient();
return await graphClient.Users[userId.ToString()].AppRoleAssignments.Request().AddAsync(
new AppRoleAssignment()
{
PrincipalId = userId,
AppRoleId = appRoleId,
ResourceId = this.graphAppSettingsProvider.GetAppRoleResourceIdAsGuid()
});
}
df0b3e71-fd2d-41a4-bfa9-0310b31395ae is Id of user from tenantB.
UPDATE:After further investigation i was able to assign App role for user from TenantB. But i had to change settings in the code that returns GraphServiceClient and provide TenantB Id and Application Service Principal Id from TenantB (instead of values from TenantA). But that's a problem. We would like to be able to assign application roles for users from any tenant and it will be not doable if we will have to provide TenantId and Service Principal Id for each tenant separately.
Is it possible to do this some how with some common settings?
This is how i get GraphServiceClient:
public async Task<GraphServiceClient> GetGraphServiceClient()
{
var clientId = this.graphAppSettingsProvider.GetClientId();
var clientSecret = this.graphAppSettingsProvider.GetClientSecret();
var tenantId = this.graphAppSettingsProvider.GetTenant();
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithTenantId(tenantId)
.Build();
string[] scopes = {"https://graph.microsoft.com/.default"};
return new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider((requestMessage) =>
{
var ar = app.AcquireTokenForClient(scopes).ExecuteAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", ar.Result.AccessToken);
return Task.FromResult(0);
}));
}
UPDATE 2
Changed a little requirements and now we just need to manage App Roles list for users from current user tenant. So, we changed permissions type from Application to Delegated to be behalf of authenticated user.
As i said earlier we have Angular app in pair with ASP.NET Core WebAPI backend. Angular app gets access token and sends it to backend in Authorizaiton header. When i attach with access token to GraphServiceClient request (header) i'm getting error "Access token validation failure. Invalid audience."
Question: is this correct flow to use access token from client for Graph API requests or should i get new access token for Graph API at backend using access token from client?
Any help/ideas appreciated. Thanks in advance!

First, you need to set up the MyAuthApp application as a multi-tenant application.
Next, run admin consent url in the browser, and then you need to log in with another tenant's administrator account and consent. The multi-tenant application will then be added to the target tenant as an enterprise application. https://login.microsoftonline.com/common/adminconsent?client_id={client-id}.
At the same time, the app role you created in tenant A will also be synchronized to the target tenant (for example, tenant B). Next, you only need to grant the app role of MyAuthApp to the users of tenant B through the Azure portal of tenant B or use ms graph api.

Related

Get bearer token with MSAL.NET to access App Service with EasyAuth

I have an Azure App Service which is authenticated using Azure AD EasyAuth.
I am trying to send a request from another App Service using C# and MSAL.NET (Microsoft.Identity.Client).
The authentication code looks like this
var app = ConfidentialClientApplicationBuilder
.Create(config.ClientId) // The Client ID in the App Registration connected to the App Service
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority)) // https://login.microsoftonline.com/tenant.onmicrosoft.com/v2.0
.WithTenantId(config.TenantId) // Tenant Id Guid
.Build();
// Used Scopes: ["https://graph.microsoft.com/.default"]
var credentials = await app.AcquireTokenForClient(config.Scopes)
.ExecuteAsync(cancellationToken);
I get a bearer token successfully, but when I try to call the App Service with token injected to the headers I get a 401 and You do not have permission to view this directory or page. :(
Update 1:
I tried #Jim Xu answer and it's still giving me 401. It returns a www-authenticate header with the following value
The resource id is the same ClientId in the App Reg
Update 2 - Solution
So to summarize the fix:
The requested scopes when calling AcquireTokenForClient should include {Application ID Uri}/.default
In EasyAuth configuration, the Allowed Token Audiences needs to be set to the Application ID Uri as well
If you want to call the Azure API app which enables easy auth, please refer to the following steps
Get the Application ID URI of the AD application you use to enable easy auth
a. In the Azure portal menu, select Azure Active Directory or search for and select Azure Active Directory from any page.
b. Select App registrations > Owned applications > View all applications in this directory. Select your web app name, and then select Overview.
code
var app = ConfidentialClientApplicationBuilder
.Create(config.ClientId) // The Client ID in the App Registration connected to the App Service
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority)) // https://login.microsoftonline.com/tenant.onmicrosoft.com/v2.0
.WithTenantId(config.TenantId) // Tenant Id Guid
.Build();
// Used Scopes: ["{Application ID URI}/.default"]
var credentials = await app.AcquireTokenForClient("{Application ID URI}/.default")
.ExecuteAsync(cancellationToken);
For more details, please refer to here.

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 monitor REST API throwing invalid token error

I created a native APP with Client credentials
AuthenticationContext authContext = new AuthenticationContext(AUTHORITY, false, service);
ClientCredential clientCred = new ClientCredential(CLIENT_ID, "myclientsecret");
//Future<AuthenticationResult> future = authContext.acquireToken("https://graph.microsoft.com", clientCred, null);
Future<AuthenticationResult> future = authContext.acquireToken("https://graph.microsoft.com", clientCred, null);
Authority is:
https://login.microsoftonline.com/{tenetID}/oauth2/authorize/
This worked fine and got a access token. Then I tried to access Azure Management REST API by setting this token as Bearer Token. Getting
401 Unauthorized
WWW-Authenticate: Bearer authorization_uri="https://login.windows.net/{tenentid}", error="invalid_token", error_description="Could not find identity for access token."
Any idea what am I doing wrong?
Another observation is, based on this create service principal documentation, when we add an app in AD it should show up in IAM --> assignment roles, some how my app is not showing up there. Seems I am missing some critical step.
If you want to call Azure Management REST API, the resource should be https://management.azure.com in your code, not the https://graph.microsoft.com which is for Microsoft Graph API.
Another observation is, based on this create service principal documentation, when we add an app in AD it should show up in IAM --> assignment roles, some how my app is not showing up there.
When you create an app in AAD, it will not be added to the Access control (IAM) of your subscription automatically, you need to add it as a role manually. Navigate to the Access control (IAM) in your subscription -> Add -> Add role assignment -> search for your service principal(AD App) by name and select a role (e.g. Owner) -> Save.

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