I am trying to use the Windows Azure Active Directory (WAAD) Graph API to add an application to my WAAD tenant. I have successfully used the API to create users. When using the API to add an application I receive an Authorization exception:
Authorization_RequestDenied: Insufficient privileges to complete the operation
Performing the same steps to add a user works without exception.
I followed the guide here: http://msdn.microsoft.com/en-us/library/windowsazure/dn151791.aspx#BKMK_Configuring and the samples here: http://code.msdn.microsoft.com/Write-Sample-App-for-79e55502 to get started.
Here is a sample of my code:
//get the tenantName
var tenantName = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// retrieve the clientId and password values from the Web.config file
var clientId = ConfigurationManager.AppSettings["ClientId"];
var password = ConfigurationManager.AppSettings["Password"];
// get a token using the helper
var token = DirectoryDataServiceAuthorizationHelper.GetAuthorizationToken(tenantName, clientId, password);
// initialize a graphService instance using the token acquired from previous step
var graphService = new DirectoryDataService(tenantName, token);
// Create and save the application
var application = new Application();
application.availableToOtherTenants = false;
application.displayName = "some display name";
application.homepage = "https://localhost/";
application.identifierUris.Add("https://localhost/");
application.replyUrls.Add("https://localhost/");
graphService.AddTodirectoryObjects(application);
graphService.SaveChanges();
Do I need to setup rights to allow the creation of Applications via the Graph API? I was unable to find a location in the Azure Management Console that allowed me to do this.
Am I using the correct code to add an Application? There are not many examples on how to work with Applications. I assume I need to use the AddTodirectoryObjects to save an Application because I am not finding an "AddTo..." method for Applications.
It seems your service principal is in the wrong role. I guess it's under User Account Administrator role. Try to add it to other role e.g.: Company Adminstrator for testing purpose...
Related
I am using Microsoft.Identity.Claim library to conenct to azure and authenticate user.
My first idea was to use AcquireTokenByIntegratedWindowsAuth method but that requires few days until network administrator people investigate how to enable single sign-in option and change that user are now "federated" and not "managed. So I now switched to AcquireTokenInteractive method because chances are that ure will be logged in, so he will just need to choose account from automatically opened browser and that's it. No big deal.
And this works:
string clientId = "xxx";
var tenantId = "yyy";
string[] scopes = new string[] { "User.Read", "User.ReadBasic.All"};
AuthenticationResult result;
var app = PublicClientApplicationBuilder.Create(clientId)
.WithRedirectUri("http://localhost")
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId).Build();
try
{
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
catch (MsalUiRequiredException) //see all possibl exceptions!
{
However, I don't receive claims inside token.
My idea is to send this token to server, then validate it, and if sucessfull create user in database and then use my own authenication mechanism I use for other users (that are not part of domain, completely separate user).
But I don't want all users from domain have access to this app.
So I would like to get claims, roles...
How to get claims, using this or any other lib given user email, or some other unique data?
Here I summarize all of the steps you may need to follow:
1. You need to register a app in azure ad for your service app, you can refer to this document
2. Then you need to register another app in ad as the client app, you can follow the steps in this document
3. After that, you need to do the steps in this document which I already provided in comments. Do it in the service app but not client app.
4. Then you can get the access token with your code and check the claim roles. Please note, do not add microsoft graph permissions into scopes in your code. You need to add api://<the client id of the registered app for service app>/.default into scopes.
5. Now you can find the claim roles in your access token.
I am try to connect an Azure Function App to the MYOB API.
I can get it to work running the Azure app from my desktop, but doesn't work when I move it to the cloud.
The published code is -
var developerKey = "YOUR API KEY";
var developerSecret = "YOUR API SECRET";
var configuration = new ApiConfiguration(developerKey, developerSecret, "http://desktop");
var oauthService = new OAuthService(configuration);
var tokens = oauthService.GetTokens(OAuthLogin.GetAuthorizationCode(configuration));
var keystore = new SimpleOAuthKeyService();
keystore.OAuthResponse = tokens;
// Get Company Files
var cfService = new CompanyFileService(configuration, null, keystore);
var companyFiles = cfService.GetRange();
The problem seems to be the line -
oauthService.GetTokens(OAuthLogin.GetAuthorizationCode(configuration));
As that function opens the MYOB login page and returns the authorization code via the HTML.
Has anyone been able to connect an Azure function to MYOB?
Thanks!
Usually there are two ways to get the authorization token:
1) User Login
2) Application Login
Application is registered and their credentials used to access an API is what you are going for.
But what I can see from their documentation is that, they don't have that process. They only have the user flow open, which is very similar to Azure SSO login where it gets access codes and based on it, it gets the token.
User flow should still be fine, if your app is the front end app and the users of the app should have access to MyOB as well.
I've created a new user account in my test AAD tenant, say testuser1#mytenant.onmicrosoft.com and set a password for it. This new account is a member of a security group that can access a specific Web API. I'm trying to write a test (a console program) that non-interactively obtains an access token using the user credentials and the app id as audience and then calls an endpoint.
How do I do that?
Update:
I'm trying to write a set of integration security tests for my Web API application. The application uses AAD groups it gets as a set of claims and treats them as roles. So I want a set of test user accounts with a known password with different roles to test behavior of an endpoint under different security contexts. The approach worked for me for years with classic AD (where I could impersonate a user using login/password pair and perform a SOAP call to a service with Windows Auth enabled).
Updated2:
I could use a set of app registrations instead of test user accounts and get a token without no problem using client_id/client_secret pair but assigning an enterprise application to a security group requires premium AAD tier which is very expensive :(
This is basically what Resource Owner Password Credentials (ROPC) grant flow is for.
You give Azure AD your app's credentials with a user's credentials and you get an access token.
This flow should not be used for authentication typically, as it exists in the standard mainly as a legacy upgrade path.
And it does not work with federated users, users with MFA or with an expired password.
However, your case of an automated test is one of the scenarios where I think its usage is acceptable.
Here is an example of the call in C#:
string tokenUrl = $"https://login.microsoftonline.com/joonasapps.onmicrosoft.com/oauth2/token";
var req = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = "23d3be1b-a671-4452-a928-78fb842cb969",
["client_secret"] = "REDACTED",
["resource"] = "https://graph.windows.net",
["username"] = "testuser#joonasapps.onmicrosoft.com",
["password"] = "REDACTED"
});
using (var client = new HttpClient())
{
var res = await client.SendAsync(req);
string json = await res.Content.ReadAsStringAsync();
}
ADAL.NET does not expose an overload for doing this AFAIK so you need to do it manually like this.
You'll need to replace the parameters with your app's credentials + your user's credentials of course.
The token URL also needs your tenant id or domain name.
Change the resource parameter to your API's client id/app ID URI.
By "non-interactively" are you referring to the login window? If so given the flow and architecture you've described, this is not possible. How else would you get the users credentials?
You should use this article as a reference when building your solution so you understand the various OAuth 2.0 flows and options, including those for a native application.
https://learn.microsoft.com/en-us/azure/active-directory/develop/authentication-scenarios#native-application-to-web-api
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);
I'm trying to read the user's data from Azure Active Directory via Microsofts' Graph API. Using the Graph Explorer I'm able to get all users but using a stand alone application I end up with an "unauthorized" response after receiving a token. I'm clearly missing some steps but it isn't obvious to me what steps that would be. Any insight would be appreciated
The code below is based off a MSFT sample:
// config values
// authority = "https://login.microsoftonline.com/{ TENANT ID }/oauth2/"
// resource uri = "https:// APP NAME .azurewebsites.net";
// graph uri = https://graph.windows.net/TENANT ID/ also tried https://graph.windows.net/v1.0/
// short form
public async void GetUsers( ADConfiguration config )
{
_authContext = new AuthenticationContext(config.GetAuthority());
_clientCredential = new ClientCredential(config.ClientId, config.ClientSecret);
AuthenticationResult result = null;
// obtain the token, this part is still successful
result = await _authContext.AcquireTokenAsync(config.ResourceUri, _clientCredential );
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
string address = config.GetGraphiUri() + "users?api-version=1.6";
// this response is always unauthorized
HttpResponseMessage response = await _httpClient.GetAsync(address);
}
In addition to answer your new problem . From you code , you are acquiring token using client credential flow. In the client credentials flow, permissions are granted directly to the application itself.
Since you are using Azure AD Graph API , you need to add application permission :
In the Azure portal, choose your application, click on Settings
In the Settings menu, choose the Required permissions section ,select Windows Azure Active Directory(Azure ad graph api) , add related application permissions your app requires .
Inside your app's blade, hit Grant Permissions to do admin consent with your admin's credential .
Your config values seem off:
Authority should be: https://login.microsoftonline.com/{TENANT ID}.
It seems to me that you are trying to use Azure AD Graph API, not Microsoft Graph API.
In that case:
Resource URI should be: https://graph.windows.net/. (MS Graph API is https://graph.microsoft.com/)