User information from Microsoft Graph - c#

Background information.
I have an application (MS Teams Bot) that I have hosted in Microsoft Azure as an "App Service". This application. This application has its own application Id. In its "Settings" I have amended the "Authentication/Authorization" to be enabled as such,
So I have created a new Azure Active Directory Application within my existing app service in Azure. Now this application has its own application ID and key. The Multi-tenant feature has been enabled on this.
This application has the following permissions,
Application permissions.
Delegated permissions.
Problem.
I have 2 unrelated environments in Azure. One (we can call this Azure A), which is for hosting test applications and the other (Azure B) is for hosting live applications and is host to our live active directory. My bot application is in Azure A but when a user from Azure B uses the bot, the bot tries to authenticate the user against the active directory it came from, it is unable to do so. My bot application gains an access token to run against the api "https://graph.microsoft.com", which it successfully acquires.
Here I have a class, which acquires an access token for my bot application to run against,
class AzureAuthenticationProvider : IAuthenticationProvider
{
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
string clientId = "client-id"; // azure ad app id
string clientSecret = "client-secret"; // azure ad app secret
string authority = "https://login.microsoftonline.com/tenant-id"; // Authentication URI. tenant-id taken from Azure B
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential creds = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
request.Headers.Add("Authorization", "Bearer " + authResult.AccessToken);
}
}
I then run the following to try and access user information from Azure B,
GraphServiceClient client = new GraphServiceClient(new AzureAuthenticationProvider());
string userId = user object id from Azure B;
User user = await client.Users[userId].Request().GetAsync();
I then receive the following error,
Microsoft.Graph.ServiceException: Code: Authorization_IdentityNotFound Message: The identity of the calling application could not be established.
So my questions are,
i) Should the client id and secret used in AuthenticateRequestAsync() be of the Azure Active Directory application or should this be taken from my bot application? These 2 are run in the same app service in Azure A.
ii) If the Azure Active Directory application has multi-tenant enabled for it, will it be able to authenticate against users from an active directory from outside of my domain?
iii) I suspect the authentication URI has to contain the tenant id of where the user is from (Azure B) and not the tenant id of where the application is hosted (Azure A), is this correct?

From the AAD registration:
clientid = Application ID of AAD Application.
clientSecret = the generated key of the AAD Application .
Yes, this is one of the use cases of the multi-tenanted solution.
In this instance, you want to use the /common endpoint(https://login.microsoftonline.com/common). This will authenticate the user against the tenant they live in.
If it were a single-tenant application, you would use /{tenant-id} in place of /common. Also, note the /common endpoint isn't supported with the client_credential OAuth flow.

Related

Is it possible to purge azure cdn endpoint using storage key?

I want to create a function which will purge a file on Azure CDN.
Here in the documentation It says How can I purge the content specifying the path.
POST https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Cdn/profiles/{profileName}/endpoints/{endpointName}/purge?api-version=2017-10-12
But the security is provided by Azure Active Directory OAuth2 Flow.
Hence I need to use clientId, secretId (from here https://blogs.msdn.microsoft.com/maheshk/2017/04/01/azure-cdn-how-to-purge-cdn-content-from-c-code/)
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/microsoft.onmicrosoft.com");
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
Task<AuthenticationResult> resultstr = authenticationContext.AcquireTokenAsync("https://management.core.windows.net/", clientCredential);
WebClient client = new WebClient();
//authentication using the Azure AD application
var token = resultstr.Result.AccessToken;
I wander Is there a way to make purge request using storage key and not clientId, secretId?
No, it is not possible. The Azure Rest API Endpoints - Purge Content is integrated with Azure AD authentication, it needs your valid credentials to get the access token.
See this link : Getting Started with REST - Register your client application with Azure AD.
Most Azure services (such as Azure Resource Manager providers and the classic deployment model) require your client code to authenticate with valid credentials before you can call the service's API. Authentication is coordinated between the various actors by Azure AD, and provides your client with an access token as proof of the authentication. The token is then sent to the Azure service in the HTTP Authorization header of subsequent REST API requests. The token's claims also provide information to the service, allowing it to validate the client and perform any required authorization.

Azure AD App Authentication Successfully Authenticating all Apps in Same Tenant Without Delegation/White-listed Permission

I am using Azure AD app to app authentication and also user authentication.
Now, from within the same AD tenant any registered app is able to call my app with both user or app token even though I have not added/white-listed the caller app. This is in contrast to what use to happen couple of years back where we had to add delegated permission in our app registration permissions to allow any app to call us with a token.
My Asp.Net Core App is using below code snippet to setup authentication.
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer((option) =>
{
option.Audience = audience;
option.Authority = authority;
option.TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
};
});
Please let me know why there is no need to add delegated permission in Azure AD app anymore?
You may have hit one of the features of Azure AD.
An app in any tenant can acquire an access token for an API in any tenant as long as they know its identifiers (tenant id + client id/app id URI).
The token will not contain any delegated permissions or application permissions, there is no way for it to get those without requiring them + someone consenting them of course.
But it will be otherwise valid.
You need to check in your API that the caller has appropriate permissions to call your API.
Check that the token contains a valid delegated permission or application permission.
Delegated permissions will be space-delimited in the scp claim, and application permissions will be in an array in the roles claim.
Failing to do these checks can leave your API vulnerable.

Authenticate with Dynamics 365 from an Azure Function

Scenario
I have a Dynamics 365 v9 organisation hosted online. I have a set of Azure Functions hosted in an Azure Function App on a different tenant to my Dynamics organisation.
I've created web hooks using the Dynamics Plugin Registration Tool, which at certain events (such as when a Contact is created in Dynamics), POST data to my Azure Functions via their endpoint URLs.
Authentication between Dynamics 365 and my Azure Functions is achieved by passing an x-functions-key value in the HTTP request's authentication HttpHeader.
The Azure Functions receive data from the event in Dynamics in the form of a RemoteExecutionContext which I can read using the following code:
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
var jsonContent = await req.Content.ReadAsStringAsync();
log.Info(jsonContent);
return req.CreateResponse(HttpStatusCode.OK);
}
Question
How can the Azure Function then authenticate back with the calling Dynamics 365 organisation to read and write data?
What I've tried
Xrm Tooling
The simplest way to authenticate would be to use the CrmServiceClient from Microsoft.Xrm.Tooling.Connector.dll. However, I don't necessarily have a username and password to provide the CrmServiceClient's constructor. Perhaps credentials could be passed securely via the HTTP POST request?
Application User
I've tried registering an Application User in Dynamics. I supply the client id and client secret to my Azure Functions, but authentication fails because the user is in a different tenant to my Azure Functions.
Considered Solutions
One object of the received jsonContent string is called ParentContext . Perhaps this can be reused to authenticate back with the calling Dynamics organisation.
Marc Schweigert has recommended using S2S and has provided a sample to his AzureFunctionApp repository. If I can get this approach to work I'll post the solution here.
I wouldn't have thought you can sensibly use the 'real' users credentials to connect to CRM.
I would use a service account to connect back into CRM. Create a new CRM
user especially for this purpose, if you make the user non-interactive you shouldn't consume a license. You can then use the credentials of that service account to connect to CRM using CrmServiceClient. Alternatively have a look at Server to Server authentication.
If you are able to deliver a user id to your Function App, you use the service account to impersonate 'real' users via the CRM web services.
To impersonate a user, set the CallerId property on an instance of
OrganizationServiceProxy before calling the service’s Web methods.
I have done something similar recently, but without relying on the Azure subscription authentication functionality for connecting back into D365. In my case calls were coming to Azure functions from other places, but the connection back is no different. Authentication does NOT pass through in any of these cases. If an AAD user authenticates to your Function application, you still need to connect to D365 using an application user, and then impersonate the user that called you.
First, make sure that the application you registered in Azure AD under App Registrations is of the type "Web app / API" and not "Native". Edit the settings of the registered app and ensure the following:
Take not of the Application ID, which I'll refer to later as appId.
Under "API Access - Required Permissions", add Dynamics CRM Online (Microsoft.CRM) and NOT Dynamics 365.
Under "API Access - Keys", create a key with an appropriate expiry. You can create multiple keys if you have multiple functions/applications connecting back as this "App". I'll refer to this key as "clientSecret" later.
If the "Keys" option isn't available, you've registered a Native app.
I stored the appId and clientSecret in the application configuration section of the Function App, and accessed them using the usual System.Configuration.ConfigurationManager.AppSettings collection.
The below examples use a call to AuthenticationParameters to find the authority and resource URLs, but you could just as easily build those URLs manually using the countless examples online. I find this will just update itself if they ever change, so less work later.
These are simple examples and I'm glossing over the need to refresh tokens and all those things.
Then to access D365 using OData:
string odataUrl = "https://org.crm6.dynamics.com/api/data/v8.2/"; // trailing slash actually matters
string appId = "some-guid";
string clientSecret = "some key";
AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;
using (HttpClient client = new HttpClient()) {
client.TimeOut = TimeSpan.FromMinutes (2);
client.DefaultRequestHeaders.Add("Authorization", authRes.CreateAuthorizationHeader ());
using (HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, $"{odataUrl}accounts?$select=name&$top=10")) {
using (HttpResponseMessage res = client.SendAsync(req).Result) {
if (res.IsSuccessStatusCode) {
Console.WriteLine(res.Content.ReadAsStringAsync().Result);
}
else {
// cry
}
}
}
}
If you want to access D365 using the Organization service, and LINQ, use the following. The two main parts that took me a while to find out are the format of that odd looking organization.svc URL, and using Microsoft.Xrm.Sdk.WebServiceClient.OrganizationWebProxyClient instead of Tooling:
string odataUrl = "https://org.crm6.dynamics.com/xrmservices/2011/organization.svc/web?SdkClientVersion=8.2"; // don't question the url, just accept it.
string appId = "some-guid";
string clientSecret = "some key";
AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;
using (OrganizationWebProxyClient webProxyClient = new OrganizationWebProxyClient(new Uri(orgSvcUrl), false)) {
webProxyClient.HeaderToken = authRes.AccessToken;
using (OrganizationServiceContext ctx = new OrganizationServiceContext((IOrganizationService)webProxyClient)) {
var accounts = (from i in ctx.CreateQuery("account") orderby i["name"] select i).Take(10);
foreach (var account in accounts)
Console.WriteLine(account["name"]);
}
}
Not sure what context you get back in your Webhook registration, not tried that yet, but just making sure that there's a bearer token in the Authorization header generally does it, and the two examples above inject it in different ways so you should be able to splice together what's needed from here.
This is something I'm curious about as well but I have not had the opportunity to experiment on this.
For your second option have you registered the application and granted consent in the target AAD?
https://learn.microsoft.com/en-us/dynamics365/customer-engagement/developer/use-multi-tenant-server-server-authentication
When they grant consent, your registered application will be added to the Azure AD Enterprise applications list and it is available to the users of the Azure AD tenant.
Only after an administrator has granted consent, you must then create the application user in the subscriber’s Dynamics 365 tenant.
I believe the root of the access issue is related to the Application's Service Principal Object (the Object local to the target Tenant)
Service Principal Object
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects#service-principal-object
In order to access resources that are secured by an Azure AD tenant, the entity that requires access must be represented by a security principal. This is true for both users (user principal) and applications (service principal). The security principal defines the access policy and permissions for the user/application in that tenant. This enables core features such as authentication of the user/application during sign-in, and authorization during resource access.
Consider the application object as the global representation of your application for use across all tenants, and the service principal as the local representation for use in a specific tenant.
HTH
-Chris
Using S2S you can use AcquireToken to retrieve the Bearer
var clientcred = new ClientCredential(clientId, clientSecret);
AuthenticationContext authContext = new AuthenticationContext(aadInstance, false);
AuthenticationResult result = authContext.AcquireToken(organizationUrl, clientcred);
token = result.AccessToken;
ExpireDate = result.ExpiresOn.DateTime;
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

Azure Graph API : Error 403 Forbidden with Azure AD B2C

I have an Azure AD B2C. Since Azure Active Directory has been migrated to new portal, I have a problem to read and write tenant users data with the Azure Graph API. Before, I had an application which was created from the old portal and which doesn't work now.
So, I created a new application from the new portal, as following :
Open "Azure Active Directory" tab
Open "App registrations"
Click "New application registration"
"Properties" tab with :
Name : GraphApi
App ID URI : https://myTenant.onmicrosoft.com/graphapi
Home page URL : https://graph.windows.net/winbizdev.onmicrosoft.com
"Reply URLs" tab with :
https:// graph.windows.net/winbizdev.onmicrosoft.com
"Required permissions" tab with :
Windows Azure Active Directory -> Check 3 elements which don't require admin
"Keys" tab with :
CLIENT_SECRET which never expires
Here is now my C# code to access user data from Azure Graph API :
_authContext = new AuthenticationContext("https://login.microsoftonline.com/myTenant.onmicrosoft.com");
_credential = new ClientCredential("<Application Client Guid>", "<Client secret which I created in "Keys" tab");
var result = await _authContext.AcquireTokenAsync("https://graph.windows.net/", _credential);
var http = new HttpClient();
var graphUrl = "https://graph.windows.net/myTenant.onmicrosoft.com/users/<My User Guid>?api-version=1.6";
var request = new HttpRequestMessage(new HttpMethod("GET"), graphUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var response = await http.SendAsync(request);
return response;
And I always obtain a 403 error Forbidden. The version of the assembly "Microsoft.IdentityModel.Clients.ActiveDirectory" is 3.13.8.99.
So, what is wrong in my configuration? What I have to do to read and write user tenant users data again with Azure Graph API?
Thanks for your help!
Alex
You are acquiring acess token using client credential flow . That means you need add related Application Permissions in Required permissions blade .
All application permissions of azure ad graph api need admin consent . Please click Grant Permissions button(login with admin's account) after adding application permissions .

Connection Issues when using AcquireTokenAsync() in Xamarin Forms Windows App with Azure AD B2C and custom policy

I am using MSAL to connect my Xamarin.Forms-Windows 8.1-App to Azure AD B2C with a custom SignInSignUp-policy (to login the user by using a custom E-Mail address and a password). The "normal" authentication against Azure AD (without custom policy) works fine, but when I use the policy, the message "We can't connect to the service you need right now. Check your network connection or try this again later." appears everytime after providing the credentials (directly within the dialog). Since the dialog does not finish correctly, I am not retrieving any exception or AuthenticationResult and so I am not able to determine the concrete issue (the internet connection itself is not the problem).
Note: I also tried this in a Xamarin.Forms-UWP-App which leads me to the same issue. Under iOS, it is working fine and the dialog finishes and closes correctly.
NuGet Versions:
MSAL (Microsoft.Identity.Client): 1.0.304142201-alpha
Xamarin.Forms: 2.3.2.118-pre1
Thats my code (simplified):
var clientId = "{My application's ClientId}";
var redirectUri = "urn:ietf:wg:oauth:2.0:oob";
var authority = "https://login.microsoftonline.com/{My Azure AD B2C}";
string[] scopes = { clientId };
var policy = "{My SignUpSignIn policy}";
var clientApplication = new PublicClientApplication (authority, clientId);
clientApplication.RedirectUri = redirectUri;
var result = await this.clientApplication.AcquireTokenAsync(scopes, string.Empty, UiOptions.SelectAccount, string.Empty, null, authority, policy);
Any ideas on this?
Thanks in advance!
Regards,
Marcel
I had the same issue. Turns out I forgot to properly set up my mobile app to allow loggin in via Active Directory
The steps I took that solved my problem were:
In Azure B2C Blade select Application and set the Reply URL to the address of the Azure Mobile Apps instance (e.g. https://myapp.azurewebsites.net), followed by /.auth/login/aad/callback
Make sure the policies used in the Azure Active Directory B2C tenant should be configured so that the Reply URL is set to the same as above
In the Azure Mobile or Services App go to settings-->authentication/autorization and make sure that a non authenticated request is set up to log in with AAD
in the same blade select the AAD provider, go to advanced and set up Client ID and Issuer URL, with the Client ID being the Application ID of the Azure Active Directory B2C tenant, and the Issuer Url being the Metadata Endpoint for the Azure Active Directory B2C policy
Once I did the above all worked flawlessly.
More detailed instructions can be found at https://developer.xamarin.com/guides/xamarin-forms/web-services/authentication/azure-ad-b2c-mobile-app/
Let me know if this helped...

Categories

Resources